openapi: 3.0.0
info:
  title: ZUCKZAPGO
  description: "ZUCKZAPGO implements a multi user and multi device REST API for WhatsApp.


    <h2>Authentication</h2><ul><li>Standard Endpoints: Include the token header with a valid user token (matches tokens stored in the users database table).</li><li>Admin Endpoints: Use the Authorization header with the admin token (set in .env as ZUCKZAPGO_ADMIN_TOKEN).</li></ul><h2>Admin Setup:</h2>You can manage users and do some tests by using the builtin dashboard [here](/dashboard).<h3>Add a Sample User via API</h3>Run this command to create a test user:<pre><code>curl -X POST http://localhost:8080/admin/users \\\ 

    -H \"Authorization: $ZUCKZAPGO_ADMIN_TOKEN\" \\\ 

    -H \"Content-Type: application/json\" \\\ 

    -d '{\"name\": \"John\", \"token\": \"Z1234ABCCXD\"}' </code> </pre>


    <h2>Webhook Configuration</h2>To receive WhatsApp messages:<ul><li>Host your own server (to process incoming messages).</li><li>Set its URL as the webhook via the [webhook](#Webhook) API call.</li></ul><h2>Phone number format:</h2><ul><li>Required: Country code (e.g. <b>5491155553934</b> for Argentina</li><li>Do not prefix with a plus sign (+), as natively whatsapp requires no plus sign prefix.</li></ul>


    <h2>Global Dispatcher Flags &amp; API Echo</h2><p>Configure tenant-level skips for the shared dispatcher via <code>/session/globaltransports/config</code> and toggle <code>Info.IsFromAPI</code> emission through <code>/session/echo/api</code>. Both flows are surfaced in the dashboard and Postman collection.</p>


    <h2>📞 Calls — LLM build guide</h2><p>Building a WhatsApp <b>voice or video call</b> integration? Open <a href=\"/api/llm.txt\" target=\"_blank\" rel=\"noopener\"><b>llm.txt</b></a> — a complete, self-contained spec you can paste into any LLM (Claude, GPT, Gemini, …) to generate a perfect calls integration (REST control + bidirectional PCM audio over WebSocket + bidirectional H.264 video over WebSocket + passkey pairing appendix). It also surfaces as the external-docs link on the <code>Call</code> tag below.</p>


    <h2>📱 Native mobile egress — operator and LLM guides</h2><p>Provision a fail-closed, TCP-only Tailscale exit route per session with <code>POST /session/network-route</code>, inspect it with <code>GET /session/network-route</code>, and — when a route cannot converge and is holding the session offline — remove it with <code>DELETE /session/network-route</code>, an explicit operator action that returns the session to legacy proxy or direct egress. Read the <a href=\"/docs/network-route.md\" target=\"_blank\" rel=\"noopener\">operator guide</a> or paste <a href=\"/api/network-route-llm.txt\" target=\"_blank\" rel=\"noopener\"><b>network-route-llm.txt</b></a> into an LLM for the complete integration contract. Auth keys are write-only and must never be persisted, returned, or logged.</p>


    <h2>🔌 Connector — embeddable pairing widget</h2><p>Embed WhatsApp pairing (QR, phone code, or passkey) in any app with one <code>&lt;script&gt;</code> — without ever exposing the real instance token to the browser. Your backend mints a short-lived ephemeral token via <code>POST /session/connector/token</code>; the browser runs <code>ZZ.connector.open({ token })</code> against the <b>Connector</b> tag's <code>/connector/*</code> routes. Paste-into-any-LLM guide: <a href=\"/api/connector-llm.txt\" target=\"_blank\" rel=\"noopener\"><b>connector-llm.txt</b></a>. Full docs hub for API users &amp; LLMs: <a href=\"/docs\" target=\"_blank\" rel=\"noopener\">/docs</a>.</p>"
  version: "2.0.0" # x-release-please-version
  termsOfService: ""

externalDocs:
  description: "📞 Calls API — paste-into-any-LLM build guide for voice, video and passkey pairing (llm.txt)"
  url: /api/llm.txt

schemes:
  - http

servers:
  - url: "/"
    description: Current host

tags:
  - name: Admin
    description: Administrative operations for managing users and user accounts
  - name: Admin Global
    description: Global system configurations including RabbitMQ, S3, and webhook settings
  - name: Admin DLQ
    description: Dead Letter Queue management for failed event processing and retry operations
  - name: Admin Event Archive
    description: Event Archive management for viewing and managing all archived events (DLQ and Success). Monitor delivery metrics, replay failed events, and maintain archive hygiene.
  - name: Admin Monitoring
    description: Real-time event monitoring, SSE streaming, and telemetry insights for administrators
  - name: Admin License
    description: License management and information for monitoring subscription status and features
  - name: System
    description: System health monitoring, metrics, and application information
  - name: Webhook
    description: Webhook configuration and event subscription management
  - name: Session
    description: Session management, authentication, QR code generation, and connection handling
  - name: Session Network Route
    description: "Fail-closed per-session mobile TCP egress through an isolated native tsnet node. Auth keys are write-only, single-use enrollment credentials and are never persisted or returned. Full operator guide: <a href=\"/docs/network-route.md\" target=\"_blank\" rel=\"noopener\">network-route.md</a>. Paste-into-any-LLM implementation guide: <a href=\"/api/network-route-llm.txt\" target=\"_blank\" rel=\"noopener\">network-route-llm.txt</a>."
    externalDocs:
      description: Native mobile egress implementation guide for operators and LLMs
      url: /api/network-route-llm.txt
  - name: Connector
    description: 'Embeddable Connector widget — mint short-lived ephemeral tokens and drive pairing (QR, phone code, passkey) from a browser without ever exposing the real instance token. Full guide: <a href="/api/connector-llm.txt" target="_blank" rel="noopener">connector-llm.txt</a> (paste into any LLM) and <a href="/docs/connector.md" target="_blank" rel="noopener">/docs/connector.md</a>.'
  - name: Session S3
    description: Per-user S3 storage configuration for media delivery and retention
  - name: Session RabbitMQ
    description: Per-user RabbitMQ configuration for event publishing and queue management
  - name: Session Skips
    description: Per-user skip media download, groups, newsletters, broadcasts, own messages, and calls configuration
  - name: Session Global Transports
    description: Manage per-transport skip flags that control how the global dispatcher enriches events (webhook, RabbitMQ, SQS, Redis, WebSocket, and S3)
  - name: Session Echo
    description: Per-user echo settings for API messages
  - name: Session Presence
    description: Per-user presence management — controls automatic PresenceUnavailable after connect, mirroring WhatsApp Web's behavior so the contact sees "last seen" instead of "online" while idle
  - name: User
    description: User profile management, contacts, and account settings
  - name: User LID
    description: Link ID (LID) mapping and conversion between phone numbers and LIDs
  - name: Chat
    description: Chat operations including message retrieval, archiving, pinning, and muting
  - name: Send
    description: "Send messages in all formats (text, media, documents, polls, buttons, lists, etc.).


      <b>API Echo Messages</b><br/>When the global flag <code>ECHO_API_MESSAGES_ENABLED</code> is true and the per-session echo setting is enabled, every successful API send emits a synthetic <code>Message</code> event through your configured transports/webhook. These events include the <code>Info.IsFromAPI: true</code> marker so downstream consumers can distinguish API-originated traffic. This ensures API-sent messages pass through the same filters/media pipeline as user-originated messages.


      <b>Per-Message Echo Control (EchoApi Field)</b><br/>All send endpoints support an optional <code>EchoApi</code> boolean field that provides <b>message-level control</b> over echo behavior. When set to <code>true</code>, the message will emit an API echo event <b>regardless</b> of global (<code>ECHO_API_MESSAGES_ENABLED</code>) or per-session (<code>echo_api_messages</code>) settings. This allows selective tracking of specific API-sent messages without enabling echo for all messages. The field is completely optional and defaults to respecting the configured echo settings when omitted. Use cases include: tracking critical business messages, monitoring specific conversation flows, or debugging individual message delivery."
  - name: Label
    description: Chat labeling and organization features
  - name: Group
    description: Group management including creation, participant management, and group settings
  - name: Community
    description: Community and sub-group management, announcements, and join requests
  - name: Business
    description: |
      WhatsApp Business features of the connected session: catalog and products, collections, cart, merchant compliance, business profile and cover photo, catalog/product/shop messages, orders, billing declarations, bots and sticker packs.

      **Typed catalog management routes** — `GET /business/linked-accounts`, `POST /business/eligibility`, `GET /business/catalog/{businessJID}/products/{productID}`, `POST /business/catalog/{businessJID}/products/query`, `GET /business/collections/{businessJID}/{collectionID}`, `POST /business/catalog`, `POST /business/products`, `PUT /business/products/{productID}`, `POST /business/products/delete`, `POST /business/products/image`, `PUT /business/products/{productID}/visibility`, `POST|PUT /business/collections…`, `POST /business/collections/delete|reorder`, `PUT /business/cart`, `POST …/appeal`, `GET|PUT /business/merchant-compliance`, `PUT /business/profile`, `POST /business/cover`, `DELETE /business/cover/{coverID}` — share one pipeline:
      - Request body (POST/PUT): exactly one JSON object, unknown fields rejected, at most 24 MiB → `400 invalid JSON body (maximum 24 MiB)`, `JSON object is required` or `exactly one JSON object is required`
      - Path IDs (`productID`, `collectionID`, `coverID`) must be non-blank and at most 256 bytes → `400 ID must contain 1-256 bytes`; `businessJID` must be `@s.whatsapp.net` or `@lid` → `400 businessJID must be a user JID`, and `limit`/`cursor` are validated on those routes
      - Validation runs before the session check, then `500 no session` / `client not connected`
      - Any WhatsApp failure, including validation inside the WhatsApp library, returns `502 business operation failed: <reason>`
      - The result is wrapped twice: `{"code":200,"data":{"data":…,"success":true},"success":true}`
      - Successful mutations clear this user's in-memory catalog cache (300 s)

      **Billing declarations** — `POST /business/order/charge/send`, `/business/order/accept`, `/business/order/status/send`, `/business/order/payment/status/send` and `/business/order/reject`:
      - Require an Enterprise license (`403`, checked first)
      - Body: exactly one JSON object with unknown fields rejected (256 KiB for billing, 16 KiB for refusal) → `400`
      - `to` must be an individual buyer JID (`@s.whatsapp.net` or `@lid`, no device) and `id` at most 128 bytes
      - Then `500 no session` / `client not connected`, `409 own device identity unavailable`, `500 buyer identity lookup failed`
      - `502 … delivery is not confirmed` means WhatsApp did not acknowledge the send: do not retry blindly, the buyer may have received it
      - Only messages are sent: no bank or payment transaction is performed (`financial_transaction_performed: false`)

      **Prices** — catalog prices are integers in thousandths of the currency (`3000000` = 3,000.00; strings on typed routes), while billing amounts use `{value, offset}` (`value/offset`).
  - name: Device
    description: Device management, platform detection, and linked device information
  - name: Privacy
    description: Privacy settings, blocklist management, and disappearing message configuration
  - name: Newsletter
    description: Newsletter/channel management, subscriptions, and newsletter messaging
  - name: Status
    description: Status/story posting in various formats (text, image, video, audio)
  - name: Call
    description: "Native VoIP calls engine (dial/answer/hangup/play/record + bidirectional s16le 16kHz PCM audio over /call/{call_id}/stream) plus legacy signaling control (reject/accept/preaccept/terminate/initiate).


      📞 **[Build a perfect calls integration with any LLM — open the llm.txt guide ↗](/api/llm.txt)** — a complete, self-contained spec you can paste into Claude / GPT / Gemini."
    externalDocs:
      description: "📞 Calls API — complete LLM build guide (llm.txt)"
      url: /api/llm.txt
  - name: Sync
    description: Synchronization and data consistency management

paths:
  /session/echo/api:
    post:
      tags:
        - Session Echo
      summary: Turn API message echo events on or off
      operationId: configureEchoApiMessages
      description: |
        Controls whether messages sent through this API also produce a synthetic `Message` event for this instance, so webhooks and transports see outgoing API messages like any other message.

        **Rules:**
        - With echo on, every successful send emits a `Message` event with `IsFromMe: true` and `Info.IsFromAPI: true`. It goes through the normal event pipeline (filters, media processing, webhook and transports).
        - Echo events are only emitted when the server-wide `ECHO_API_MESSAGES_ENABLED` environment variable is `true` **and** this flag is on.
        - A single send can force the echo regardless of both settings with `EchoApi`, `echoApi` or `echo_api` set to `true` in its request body.
        - Because echo events are own messages, `POST /session/skipownmessages/config` with `enabled: true` drops them.
        - `enabled` omitted (for example `{}`) is treated as `false` and disables echo.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EchoAPISettingsRequest"
            examples:
              enable:
                summary: Emit events for API sends
                description: Webhook consumers receive the messages they sent through the API, flagged with `Info.IsFromAPI`.
                value:
                  enabled: true
              disable:
                summary: Stop emitting events for API sends
                description: Only messages received or sent from other devices generate events.
                value:
                  enabled: false
      responses:
        "200":
          description: Flag stored and user configuration cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EchoAPISettingsUpdateResponse"
              example:
                code: 200
                data:
                  Details: Echo API messages configuration updated successfully
                  EchoAPIMessages: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be stored or the user configuration cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update echo api messages configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to refresh configuration cache
                    success: false
    get:
      tags:
        - Session Echo
      summary: Get the API message echo setting
      operationId: getEchoApiMessagesConfig
      description: |
        Returns whether this instance emits `Message` events for messages sent through the API.

        **Rules:**
        - `EchoAPIMessages` is the per-instance flag (`false` when never set).
        - Echo events are only emitted when the server-wide `ECHO_API_MESSAGES_ENABLED` is also `true`; that variable is not included in this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored echo flag.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EchoAPISettingsResponse"
              example:
                code: 200
                data:
                  EchoAPIMessages: true
                  Details: Echo API messages configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get echo api messages configuration
                success: false
  /admin/users:
    get:
      tags:
        - Admin
      summary: List all users
      description: |
        Returns every user (instance) stored in the gateway database, ordered by name, with its complete per-user configuration and live connection flags.

        **Features:**
        - `connected` and `loggedIn` come from the in-memory WhatsApp client; both are `false` when the instance has no client in this process.
        - Includes webhook, event subscription, proxy, S3, RabbitMQ, WhatsApp client identity, skip filters, call rejection and global transport skip settings.
        - `avatar_url` uses the cached value; missing avatars are fetched from WhatsApp in parallel for paired instances and saved back to the database.

        **Rules:**
        - Admin token required in the `Authorization` header (a `Bearer ` prefix is accepted).
        - Credentials are returned as stored: `token`, `s3_config.access_key` and any password inside `rabbitmq_config.url` are **not masked**. The S3 secret key is never returned.
        - `rabbitmq_config.queue_arguments` and `exchange_arguments` are returned as JSON-encoded strings.
        - An empty database returns `data: []`.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: All users with their full configuration.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminUserListResponse"
              example:
                code: 200
                data:
                  - id: bec45bb93cbd24cbec32941ec3c93a12
                    name: store-support
                    avatar_url: https://pps.whatsapp.net/v/t61.24694-24/example.jpg
                    token: a1b2c3d4e5f6a7b8c9d0
                    webhook: https://hooks.example.com/zuckzapgo
                    jid: "5511999999999:12@s.whatsapp.net"
                    qrcode: ""
                    connected: true
                    loggedIn: true
                    expiration: 0
                    proxy_url: ""
                    events: Message,ReadReceipt,Connected
                    skip_media_download: false
                    skip_groups: true
                    skip_newsletters: true
                    skip_broadcasts: true
                    skip_own_messages: false
                    echo_api_messages: false
                    skip_calls: false
                    call_reject_message: Sorry, I cannot take calls at the moment.
                    call_reject_type: busy
                    globalTransportSkips:
                      skipGlobalWebhook: false
                      skipGlobalRabbitMQ: true
                      skipGlobalSQS: false
                      skipGlobalRedis: false
                      skipGlobalWebSocket: false
                      skipGlobalS3: false
                    isFromAPI: false
                    auto_presence_unavailable: false
                    proxy_config:
                      enabled: false
                      proxy_url: ""
                    s3_config:
                      enabled: true
                      endpoint: https://s3.us-east-1.amazonaws.com
                      region: us-east-1
                      bucket: zuckzapgo-media
                      access_key: AKIAEXAMPLEACCESSKEY
                      path_style: false
                      public_url: ""
                      media_delivery: both
                      retention_days: 30
                      disable_acl: true
                    rabbitmq_config:
                      enabled: false
                      url: ""
                      exchange: ""
                      exchange_type: ""
                      queue: ""
                      queue_type: ""
                      routing_key: ""
                      events: ""
                      durable: false
                      auto_delete: false
                      exclusive: false
                      no_wait: false
                      delivery_mode: 0
                      dead_letter_exchange: ""
                      dead_letter_routing_key: ""
                      message_ttl: 0
                      max_length: 0
                      max_length_bytes: 0
                      queue_arguments: ""
                      exchange_arguments: ""
                    whatsapp_config:
                      wa_version: ""
                      wa_platform: WEB
                      wa_release_channel: RELEASE
                      wa_web_sub_platform: WEB_BROWSER
                      wa_os_name: Mac OS 10
                      wa_os_version: 10.15.7
                      wa_device_name: Desktop
                      wa_manufacturer: Apple
                      wa_device_board: ""
                      wa_locale_language: en
                      wa_locale_country: US
                      wa_mcc: "000"
                      wa_mnc: "000"
                      wa_connect_type: WIFI_UNKNOWN
                      wa_platform_type: DESKTOP
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the users query or row scan failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: problem accessing DB
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token, so admin routes are disabled."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    post:
      tags:
        - Admin
      summary: Create a user (instance)
      description: |
        Creates a new user (WhatsApp instance) with its API token and optional webhook, event subscription, proxy, S3, RabbitMQ, WhatsApp client identity, skip filters, call rejection and global transport skip settings. The instance is created unpaired; connect it afterwards with `POST /session/connect` using the new token.

        **Features:**
        - `s3Config` accepts snake_case keys (preferred) and camelCase aliases; when both are sent, snake_case wins.
        - `networkRoute` provisions the same native mobile egress as `POST /session/network-route` before the instance may connect.
        - With `rabbitmqConfig.enabled: true` or `s3Config.enabled: true` the per-user client is initialized right away; initialization failures are only logged.

        **Rules:**
        - `token` must be unique: an existing token returns **409**. `name` and `token` are not validated for emptiness.
        - Every comma-separated name in `events` must be a supported event type (case-sensitive, surrounding spaces ignored); otherwise **400** `invalid event type` with a `details` hint. An empty value subscribes to nothing.
        - `callRejectMessage` defaults to `Sorry, I cannot take calls at the moment.` and `callRejectType` to `busy`.
        - `networkRoute` cannot be combined with `proxyConfig` (**400** `network_route_conflicts_with_proxy`). Invalid route fields return **400** with the specific `network_route_*` code.
        - The route manager is checked before the user is stored (**503** `network_route_manager_unavailable`). A route that fails after the user was stored returns **503** with `data.userId` and the blocked route state; the one-use `authKey` is consumed and never returned. Submit a higher `routeVersion` with a new key or delete the user.
        - The response echoes the stored settings in snake_case. The S3 secret key is never echoed.
      security:
        - AdminAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUser"
            examples:
              minimal:
                summary: Minimal instance
                description: Only a name and a unique token; no webhook and no event subscription yet.
                value:
                  name: store-support
                  token: a1b2c3d4e5f6a7b8c9d0
              webhook_and_filters:
                summary: Webhook with event filters
                description: Subscribes to three event types, skips group, newsletter and broadcast traffic, and rejects calls with a custom message.
                value:
                  name: store-support
                  token: a1b2c3d4e5f6a7b8c9d0
                  webhook: https://hooks.example.com/zuckzapgo
                  events: Message,ReadReceipt,Connected
                  skipGroups: true
                  skipNewsletters: true
                  skipBroadcasts: true
                  skipCalls: true
                  callRejectMessage: We only answer by chat. Please send a message.
                  callRejectType: decline
              s3_and_rabbitmq:
                summary: S3 media storage and RabbitMQ publishing
                description: Stores media in S3 (delivered as base64 and URL) and publishes all events to a per-user RabbitMQ exchange.
                value:
                  name: store-support
                  token: a1b2c3d4e5f6a7b8c9d0
                  events: All
                  s3Config:
                    enabled: true
                    endpoint: https://s3.us-east-1.amazonaws.com
                    region: us-east-1
                    bucket: zuckzapgo-media
                    access_key: AKIAEXAMPLEACCESSKEY
                    secret_key: exampleSecretKey0123456789
                    media_delivery: both
                    retention_days: 30
                    disable_acl: true
                  rabbitmqConfig:
                    enabled: true
                    url: amqp://zuckzapgo:example-password@rabbitmq.internal:5672/
                    exchange: zuckzapgo.store-support
                    exchange_type: topic
                    queue: zuckzapgo.store-support.events
                    queue_type: quorum
                    routing_key: events.#
                    events: All
                    durable: true
                    delivery_mode: 2
                    dead_letter_exchange: dlx.zuckzapgo
                    message_ttl: 86400000
              mobile_network_route:
                summary: Instance behind a native mobile route
                description: Prepares a fail-closed mobile egress before the instance may connect. The auth key is a one-use placeholder.
                value:
                  name: pilot-mobile-01
                  token: f0e1d2c3b4a5968778695a4b3c2d1e0f
                  events: All
                  networkRoute:
                    mode: mobile
                    authKey: tskey-auth-REDACTED
                    exitNodeId: nStableMobileExit01
                    routeVersion: 1
                    idempotencyKey: provision-pilot-0001
                    failClosed: true
                    tcpOnly: true
      responses:
        "201":
          description: User created. `data` echoes the stored configuration; `network_route` is present only when a route was requested and is ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateUserResponse"
              example:
                code: 201
                data:
                  id: bec45bb93cbd24cbec32941ec3c93a12
                  name: pilot-mobile-01
                  token: f0e1d2c3b4a5968778695a4b3c2d1e0f
                  webhook: ""
                  expiration: 0
                  events: All
                  proxy_config:
                    enabled: false
                    proxy_url: ""
                  s3_config:
                    enabled: false
                    endpoint: ""
                    region: ""
                    bucket: ""
                    access_key: ""
                    path_style: false
                    public_url: ""
                    media_delivery: ""
                    retention_days: 0
                    disable_acl: false
                  rabbitmq_config:
                    enabled: false
                    url: ""
                    exchange: ""
                    exchange_type: ""
                    queue: ""
                    queue_type: ""
                    routing_key: ""
                    events: ""
                    durable: false
                    auto_delete: false
                    exclusive: false
                    no_wait: false
                    delivery_mode: 0
                    dead_letter_exchange: ""
                    dead_letter_routing_key: ""
                    message_ttl: 0
                    max_length: 0
                    max_length_bytes: 0
                    queue_arguments: null
                    exchange_arguments: null
                  whatsapp_config:
                    wa_version: ""
                    wa_platform: ""
                    wa_release_channel: ""
                    wa_web_sub_platform: ""
                    wa_os_name: ""
                    wa_os_version: ""
                    wa_device_name: ""
                    wa_manufacturer: ""
                    wa_device_board: ""
                    wa_locale_language: ""
                    wa_locale_country: ""
                    wa_mcc: ""
                    wa_mnc: ""
                    wa_connect_type: ""
                    wa_platform_type: ""
                  skip_media_download: false
                  skip_groups: false
                  skip_newsletters: false
                  skip_broadcasts: false
                  skip_own_messages: false
                  echo_api_messages: false
                  skip_calls: false
                  call_reject_message: Sorry, I cannot take calls at the moment.
                  call_reject_type: busy
                  globalTransportSkips:
                    skipGlobalWebhook: false
                    skipGlobalRabbitMQ: false
                    skipGlobalSQS: false
                    skipGlobalRedis: false
                    skipGlobalWebSocket: false
                    skipGlobalS3: false
                  isFromAPI: false
                  auto_presence_unavailable: false
                  network_route:
                    mode: mobile
                    exitNodeId: nStableMobileExit01
                    desiredVersion: 1
                    appliedVersion: 1
                    failClosed: true
                    tcpOnly: true
                    status: ready
                    ready: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, an event name is not supported, or `networkRoute` is invalid or combined with `proxyConfig`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                invalid_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid request payload
                    success: false
                invalid_event_type:
                  summary: Unsupported name in events
                  value:
                    code: 400
                    details: "invalid event: Mesage"
                    error: invalid event type
                    success: false
                network_route_conflicts_with_proxy:
                  summary: networkRoute sent together with proxyConfig
                  value:
                    code: 400
                    error: network_route_conflicts_with_proxy
                    success: false
                network_route_auth_key_invalid:
                  summary: authKey is not a valid one-use key
                  value:
                    code: 400
                    error: network_route_auth_key_invalid
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: another user already uses this token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 409
                error: user with this token already exists
                success: false
        "500":
          description: "Internal Server Error: the token lookup or the insert failed, or a user ID could not be generated."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                database_error:
                  summary: Database failure
                  value:
                    code: 500
                    error: database error
                    success: false
                id_generation_failed:
                  summary: Random ID generation failed
                  value:
                    code: 500
                    error: failed to generate user ID
                    success: false
        "503":
          description: "Service Unavailable: admin routes are disabled, the network route manager is not available (nothing stored), or the route could not be prepared after the user was stored (blocked user, `data.userId` returned)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateUserNetworkRouteFailureResponse"
              examples:
                route_prepare_failed:
                  summary: User stored in blocked state
                  value:
                    code: 503
                    data:
                      userId: bec45bb93cbd24cbec32941ec3c93a12
                      network_route:
                        mode: mobile
                        exitNodeId: nStableMobileExit01
                        desiredVersion: 1
                        appliedVersion: 0
                        failClosed: true
                        tcpOnly: true
                        status: blocked
                        ready: false
                        errorCode: network_route_prepare_failed
                    details: user persisted in blocked state; submit a higher routeVersion with a new one-use auth key or delete this user
                    error: network_route_prepare_failed
                    success: false
                route_manager_unavailable:
                  summary: Route manager not running, nothing stored
                  value:
                    code: 503
                    error: network_route_manager_unavailable
                    success: false
                admin_not_configured:
                  summary: Gateway started without an admin token
                  value:
                    code: 503
                    error: admin not configured
                    success: false
  /admin/instances/status:
    get:
      tags:
        - Admin
      summary: List instance connection status
      description: |
        Returns a lightweight, paginated connection view of the instances, ordered by name and ID. Use it for dashboards and health polling instead of `GET /admin/users`, which loads every configuration column.

        **Features:**
        - Uses the live WhatsApp client when the instance has one in this process (`source: runtime`); otherwise falls back to the stored `connected` flag (`source: database`, `loggedIn: false`).
        - `connectionHealth` summarizes the state: `connected`, `connected_not_logged`, `disconnected` or `no_client`.
        - `qrcode` (base64 PNG data URL) and `qrcode_text` are filled only while the instance is not connected or not logged in.
        - Pagination metadata is returned in the top-level `pagination` object; `total` only with `include_total=true`.

        **Rules:**
        - `limit` must be a positive integer (values above 1000 are capped to 1000); `offset` must be zero or positive; `include_total` must be a boolean. Anything else returns **400**.
        - `ids` filters by a comma-separated list of user IDs (spaces and duplicates ignored). A value with no usable ID returns an empty page.
      security:
        - AdminAuth: []
      parameters:
        - name: limit
          in: query
          required: false
          description: Page size. Positive integer; values above 1000 are capped to 1000.
          schema:
            type: integer
            default: 200
            minimum: 1
            maximum: 1000
          example: 50
        - name: offset
          in: query
          required: false
          description: Number of instances to skip. Use `pagination.next_offset` from the previous page.
          schema:
            type: integer
            default: 0
            minimum: 0
          example: 0
        - name: ids
          in: query
          required: false
          description: Comma-separated user IDs to look up selectively.
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12,4fa3c1d2e5b6a7980c1d2e3f4a5b6c7d
        - name: include_total
          in: query
          required: false
          description: "`true` adds `pagination.total` (runs an extra COUNT query)."
          schema:
            type: boolean
            default: false
          example: true
      responses:
        "200":
          description: One page of instance status entries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminInstanceStatusResponse"
              examples:
                first_page:
                  summary: First page with more results
                  value:
                    code: 200
                    data:
                      - id: bec45bb93cbd24cbec32941ec3c93a12
                        name: store-support
                        jid: "5511999999999:12@s.whatsapp.net"
                        qrcode: ""
                        qrcode_text: ""
                        connected: true
                        loggedIn: true
                        hasClient: true
                        connectionHealth: connected
                        lastSuccessfulConnect: 1757862000
                        autoReconnectErrors: 0
                        enableAutoReconnect: true
                        source: runtime
                        timestamp: "2026-09-15T12:00:00.123456789Z"
                      - id: 4fa3c1d2e5b6a7980c1d2e3f4a5b6c7d
                        name: store-sales
                        jid: ""
                        qrcode: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA
                        qrcode_text: 2@Xk3vQm9pZXhhbXBsZQ==,exampleKey=,exampleIdentity=,exampleAdv=
                        connected: false
                        loggedIn: false
                        hasClient: false
                        connectionHealth: no_client
                        lastSuccessfulConnect: 0
                        autoReconnectErrors: 0
                        enableAutoReconnect: false
                        source: database
                        timestamp: "2026-09-15T12:00:00.123456789Z"
                    pagination:
                      limit: 2
                      offset: 0
                      has_more: true
                      next_offset: 2
                      total: 5
                    success: true
                empty_ids_filter:
                  summary: ids without any usable value
                  value:
                    code: 200
                    data: []
                    pagination:
                      limit: 200
                      offset: 0
                      has_more: false
                      next_offset: null
                    success: true
        "400":
          description: "Bad Request: `limit`, `offset` or `include_total` could not be parsed or is out of range."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_limit:
                  summary: limit is not a positive integer
                  value:
                    code: 400
                    error: invalid limit parameter
                    success: false
                invalid_offset:
                  summary: offset is negative or not a number
                  value:
                    code: 400
                    error: invalid offset parameter
                    success: false
                invalid_include_total:
                  summary: include_total is not a boolean
                  value:
                    code: 400
                    error: invalid include_total parameter
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the status or count query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: database error
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/users/{id}:
    get:
      tags:
        - Admin
      summary: Get a user by ID
      description: |
        Returns one user with the same full configuration and live connection flags as `GET /admin/users`, filtered by the stored user ID.

        **Rules:**
        - `data` is always an **array**: one element when the ID exists, and an empty array (still **200**) when it does not. This route never returns 404.
        - Credentials are returned as stored, exactly as in the list route (`token`, `s3_config.access_key` and any RabbitMQ URL password are not masked; the S3 secret key is never returned).
        - Admin token required in the `Authorization` header.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Stored user ID (32 hexadecimal characters), as returned by `POST /admin/users`.
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
      responses:
        "200":
          description: Array with the matching user, or an empty array when the ID does not exist.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminUserListResponse"
              examples:
                found:
                  summary: User exists
                  value:
                    code: 200
                    data:
                      - id: bec45bb93cbd24cbec32941ec3c93a12
                        name: store-support
                        avatar_url: ""
                        token: a1b2c3d4e5f6a7b8c9d0
                        webhook: https://hooks.example.com/zuckzapgo
                        jid: "5511999999999:12@s.whatsapp.net"
                        qrcode: ""
                        connected: true
                        loggedIn: true
                        expiration: 0
                        proxy_url: socks5://proxy.example.com:1080
                        events: All
                        skip_media_download: true
                        skip_groups: false
                        skip_newsletters: false
                        skip_broadcasts: false
                        skip_own_messages: false
                        echo_api_messages: false
                        skip_calls: false
                        call_reject_message: Sorry, I cannot take calls at the moment.
                        call_reject_type: busy
                        globalTransportSkips:
                          skipGlobalWebhook: false
                          skipGlobalRabbitMQ: false
                          skipGlobalSQS: false
                          skipGlobalRedis: false
                          skipGlobalWebSocket: false
                          skipGlobalS3: false
                        isFromAPI: false
                        auto_presence_unavailable: false
                        proxy_config:
                          enabled: true
                          proxy_url: socks5://proxy.example.com:1080
                        s3_config:
                          enabled: false
                          endpoint: ""
                          region: ""
                          bucket: ""
                          access_key: ""
                          path_style: false
                          public_url: ""
                          media_delivery: base64
                          retention_days: 0
                          disable_acl: false
                        rabbitmq_config:
                          enabled: true
                          url: amqp://zuckzapgo:example-password@rabbitmq.internal:5672/
                          exchange: zuckzapgo.store-support
                          exchange_type: topic
                          queue: zuckzapgo.store-support.events
                          queue_type: quorum
                          routing_key: events.#
                          events: All
                          durable: true
                          auto_delete: false
                          exclusive: false
                          no_wait: false
                          delivery_mode: 2
                          dead_letter_exchange: dlx.zuckzapgo
                          dead_letter_routing_key: dlq.events
                          message_ttl: 86400000
                          max_length: 100000
                          max_length_bytes: 104857600
                          queue_arguments: '{"x-overflow":"reject-publish"}'
                          exchange_arguments: "{}"
                        whatsapp_config:
                          wa_version: ""
                          wa_platform: WEB
                          wa_release_channel: RELEASE
                          wa_web_sub_platform: WEB_BROWSER
                          wa_os_name: Mac OS 10
                          wa_os_version: 10.15.7
                          wa_device_name: Desktop
                          wa_manufacturer: Apple
                          wa_device_board: ""
                          wa_locale_language: en
                          wa_locale_country: US
                          wa_mcc: "000"
                          wa_mnc: "000"
                          wa_connect_type: WIFI_UNKNOWN
                          wa_platform_type: DESKTOP
                    success: true
                not_found:
                  summary: Unknown ID (still 200)
                  value:
                    code: 200
                    data: []
                    success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the user query or row scan failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: problem accessing DB
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    put:
      tags:
        - Admin
      summary: Update a user's event delivery preferences
      description: |
        Partially updates the per-user event filtering flags of the user identified by `id`, without touching any other setting. The change applies to the next event without a restart or reconnect.

        **Rules:**
        - Every body field is an optional boolean: only the fields present are written, absent fields keep their stored value. Unknown fields are ignored.
        - At least one known field is required, otherwise **400** `no updatable preference in payload`.
        - Field to column mapping: `skipMedia` → `skip_media_download`, `skipGroups` → `skip_groups`, `skipNewsletters` → `skip_newsletters`, `skipBroadcasts` → `skip_broadcasts`, `skipOwnMessages` → `skip_own_messages`, `echoApiMessages` → `echo_api_messages`, `skipCalls` → `skip_calls`.
        - The body is validated before the user lookup, so an unknown ID with an empty body returns 400, not 404.

        **Side Effects:**
        - The user's token cache entry is dropped so the next request or event reloads the preferences from the database.
        - The response has no `data` key; `updated_fields` is the number of columns written.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Stored user ID (32 hexadecimal characters).
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminUserPrefsRequest"
            examples:
              skip_media:
                summary: Stop inlining media in events
                description: For an integration that downloads media on demand through `/chat/download*`, so the base64 payload in events is not needed.
                value:
                  skipMedia: true
              several:
                summary: Update several flags at once
                description: Filters out group, newsletter and broadcast traffic while keeping call events.
                value:
                  skipGroups: true
                  skipNewsletters: true
                  skipBroadcasts: true
                  skipCalls: false
      responses:
        "200":
          description: Preferences written. The envelope carries `updated_fields` and no `data` key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminUserPrefsResponse"
              examples:
                one_field:
                  summary: One flag written
                  value:
                    code: 200
                    success: true
                    updated_fields: 1
                four_fields:
                  summary: Four flags written
                  value:
                    code: 200
                    success: true
                    updated_fields: 4
        "400":
          description: "Bad Request: the body is not valid JSON (including non-boolean values) or carries no known preference."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not valid JSON or a value is not a boolean
                  value:
                    code: 400
                    error: invalid request payload
                    success: false
                no_field:
                  summary: No known preference in the body
                  value:
                    code: 400
                    error: no updatable preference in payload
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no user with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: user not found
                success: false
        "500":
          description: "Internal Server Error: the user lookup or the update failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                lookup_failed:
                  summary: User lookup failed
                  value:
                    code: 500
                    error: database error
                    success: false
                update_failed:
                  summary: Update statement failed
                  value:
                    code: 500
                    error: failed to update user preferences
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    delete:
      tags:
        - Admin
      summary: Delete a user (database row only)
      description: |
        Deletes the user row identified by `id` and tears down its runtime in this process. Use it to remove an instance quickly; use `DELETE /admin/users/{id}/full` to also log out and purge stored data.

        **Behavior:**
        - Takes the per-user session lock, stops the live WhatsApp client and releases its resources, even when the ID does not exist.
        - Revokes the native network route of the user, if any. A revoke failure is only logged and does not abort the deletion.
        - Deletes the row and immediately revokes the user's token, so it stops authenticating right away.

        **Rules:**
        - The WhatsApp device is **not** logged out and the whatsmeow device store, S3 objects, RabbitMQ queues and local files are **not** purged.
        - **404** when no row matched the ID; the body then carries `details`.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Stored user ID (32 hexadecimal characters).
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
      responses:
        "200":
          description: User row deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminUserDeleteResponse"
              example:
                code: 200
                data:
                  id: bec45bb93cbd24cbec32941ec3c93a12
                details: user deleted successfully
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no user with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 404
                details: "No user found with ID: bec45bb93cbd24cbec32941ec3c93a12"
                error: user not found
                success: false
        "500":
          description: "Internal Server Error: the delete statement failed or its affected row count could not be read."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                database_error:
                  summary: Delete statement failed
                  value:
                    code: 500
                    error: database error
                    success: false
                verify_failed:
                  summary: Affected row count unavailable
                  value:
                    code: 500
                    error: Failed to verify deletion
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/users/{id}/full:
    delete:
      tags:
        - Admin
      summary: Delete a user and purge all of its data
      description: |
        Completely removes a user: logs the WhatsApp device out, stops the runtime, and purges its database rows, device store, local files, S3 objects and RabbitMQ queues.

        **Behavior (in order):**
        - Checks that the user exists (**404** otherwise) and takes the per-user session lock.
        - Logs out from WhatsApp when the client is connected; a logout failure falls through to a forced device-store deletion.
        - Stops the client, clears the stored connection flag and QR codes, and revokes the native network route.
        - Deletes the whatsmeow device, rows in `events_archive`, `monitoring_events` and `whatsmeow_privacy_tokens`, and the user row; then revokes the token and clears the S3 client and avatar caches.
        - Removes the local `files/{id}` and `files/user_{id}` directories, then deletes the user's S3 objects (2 minute limit) and RabbitMQ queues (30 second limit).

        **Rules:**
        - Only a failure to check existence or to delete the user row aborts the request (**500** with `details`). Failures in every other step are logged and the response is still **200**.
        - `data.name` and `data.jid` are empty when the stored configuration could not be read before deletion.
        - This operation is irreversible and can take minutes when S3 holds many objects.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Stored user ID (32 hexadecimal characters).
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
      responses:
        "200":
          description: User removed; all phases ran (individual phase failures are only logged).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminUserFullDeleteResponse"
              example:
                code: 200
                data:
                  id: bec45bb93cbd24cbec32941ec3c93a12
                  jid: "5511999999999:12@s.whatsapp.net"
                  name: store-support
                details: user instance removed completely (all phases completed)
                success: true
        "400":
          description: "Bad Request: the ID segment is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: missing ID
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no user with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 404
                details: "No user found with ID: bec45bb93cbd24cbec32941ec3c93a12"
                error: user not found
                success: false
        "500":
          description: "Internal Server Error: the existence check or the final user row deletion failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                existence_check_failed:
                  summary: Existence check failed
                  value:
                    code: 500
                    details: problem checking user existence
                    error: database error
                    success: false
                row_delete_failed:
                  summary: User row deletion failed
                  value:
                    code: 500
                    details: failed to delete user from database
                    error: database error
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/license/info:
    get:
      tags:
        - Admin License
      summary: Get license information
      description: |
        Returns the license state cached by the license validator, with live user counts from the database, for display in the admin dashboard.

        **Features:**
        - `users_count` and `connected_users_count` are counted live; when the query fails the cached values are used.
        - `status` is `active`, `expiring_soon` (7 days or less), `grace_period` or `expired`, unless the license server supplied its own status.
        - Includes the last validation outcome (`result`, `message`, `http_status`, `last_error`, `last_context`) and IP allowlist details when the license server returned them.

        **Rules:**
        - The body is the raw object, **not** wrapped in the `{code, data, success}` envelope. Errors use `{code, error, success}`.
        - `license_key` is returned in full; the dashboard decides whether to hide it.
        - `status_message` and `features` are currently produced in Portuguese by the gateway; the example shows their English meaning.
        - `grace_period_end` is always present and is `0001-01-01T00:00:00Z` outside the grace period.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: License information (raw object, no envelope).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminLicenseInfo"
              example:
                valid: true
                installation_id: inst_1234567890abcdef
                customer_id: cust_abcdef1234567890
                installation_name: Production Server
                plan_type: ENTERPRISE
                plan_id: plan_enterprise_monthly
                subscription_id: sub_0123456789abcdef
                expires_at: "2026-12-31T23:59:59Z"
                max_instances: 10
                license_key: ZUCKZAP-ENT-2026-EXAMPLE-KEY0-0000
                instance_id: zuckzapgo-prod-01
                last_validation: "2026-09-15T12:00:00Z"
                last_attempt: "2026-09-15T12:00:00Z"
                days_until_expiry: 107
                is_in_grace_period: false
                grace_period_end: "0001-01-01T00:00:00Z"
                status: active
                status_message: License active and valid
                features:
                  - Complete RESTful API
                  - Multiple instances
                  - Real-time events
                  - Priority support
                  - Advanced integrations
                  - 24/7 support
                  - Guaranteed SLA
                  - Interactive buttons
                support_email: support@example.com
                documentation_url: https://github.com/guilhermejansen/use-zuckzapgo
                users_count: 4
                connected_users_count: 3
                result: active
                message: License active and valid
                http_status: 200
                client_ip: 203.0.113.10
                allowlisted_ips:
                  - 203.0.113.10
                active_instances:
                  - instance_id: zuckzapgo-prod-01
                    ip_address: 203.0.113.10
                    version: 1.20.4
                    last_seen: "2026-09-15T12:00:00Z"
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the license validator was not initialized at startup."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: License validator not initialized
                success: false
        "503":
          description: "Service Unavailable: the license cache file cannot be read yet, or the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                cache_unavailable:
                  summary: License cache not available
                  value:
                    code: 503
                    error: License information not available
                    success: false
                admin_not_configured:
                  summary: Gateway started without an admin token
                  value:
                    code: 503
                    error: admin not configured
                    success: false
  /newsletter/list:
    get:
      tags:
        - Newsletter
      summary: List followed channels with raw WhatsApp metadata
      description: |
        Returns every channel (newsletter) this account follows or owns, with the metadata exactly as WhatsApp returns it
        (`xwa2_newsletter_subscribed`). Use it when you need fields that `GET /newsletter/subscribed` flattens away,
        such as the channel state, verification badge, picture handles or reaction settings.

        **Features:**
        - One entry per channel in `data.Newsletter`, in the order returned by WhatsApp; the array is empty when the account follows no channel.
        - `thread_metadata` carries name, description, invite code, creation time, subscriber count and picture info.
        - `viewer_metadata` carries this account's `role` (`owner`, `admin`, `subscriber`, `guest`) and `mute` state; it may be `null`.

        **Rules:**
        - Numeric values such as `subscribers_count` and `creation_time` are JSON strings, as sent by WhatsApp.
        - Failures reported by WhatsApp use a legacy envelope: `success` stays `true` and the message is the string in `data`.
        - For a simplified list (`jid`, `name`, `subscribers`, `muted`...) use `GET /newsletter/subscribed`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Followed channels with the raw WhatsApp metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterRawListResponse"
              example:
                code: 200
                data:
                  Newsletter:
                    - id: 120363000000000022@newsletter
                      state:
                        type: active
                      thread_metadata:
                        creation_time: "1757900000"
                        invite: 0029VaExampleInviteCode01
                        name:
                          text: Product Updates
                          id: "1757900000123456"
                          update_time: "1757900000123456"
                        description:
                          text: Release notes and product news
                          id: "1757900000654321"
                          update_time: "1757900000654321"
                        subscribers_count: "1500"
                        verification: unverified
                        picture:
                          url: https://mmg.whatsapp.net/v/t61.24694-24/example-channel-picture.jpg
                          id: "1757900001"
                          type: image
                          direct_path: /v/t61.24694-24/example-channel-picture.jpg
                          hash: null
                        preview:
                          url: https://mmg.whatsapp.net/v/t61.24694-24/example-channel-preview.jpg
                          id: "1757900001"
                          type: preview
                          direct_path: /v/t61.24694-24/example-channel-preview.jpg
                          hash: null
                        settings:
                          reaction_codes:
                            value: all
                      viewer_metadata:
                        mute: "off"
                        role: owner
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error. A WhatsApp query failure uses the legacy envelope (message string in `data`, `success: true`). The `no session` case uses the standard `ErrorEnvelope` (`error`, `success: false`), shown in the `no_session` example."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                list_failed:
                  summary: WhatsApp query failed (legacy envelope)
                  value:
                    code: 500
                    data: "failed to get newsletter list: websocket not connected"
                    success: true
  /newsletter/create:
    post:
      tags:
        - Newsletter
      summary: Create a channel
      description: |
        Creates a WhatsApp channel (newsletter) owned by this account, optionally with a description and a picture.
        The new channel JID is returned in `data.jid` and can be used right away with `POST /newsletter/send`.

        **Rules:**
        - The body must be exactly one JSON object of at most 24 MiB; unknown keys are ignored.
        - `name` is required and cannot be blank. It is cut to its first 25 **bytes**, which may split a multi-byte character; `data.name` shows the value actually used.
        - `picture` accepts a base64 data URL or an HTTP(S) URL of a JPEG, PNG, GIF or WebP image. It is converted to a JPEG of at most 640x640 pixels before upload.
        - The account must have accepted the channel terms first (`POST /newsletter/accept-tos`); otherwise WhatsApp refuses the creation.

        **Error envelopes:**
        - Body and `name` validation errors and `no session` use the standard error envelope.
        - Picture errors (**400**) and a creation refused by WhatsApp (**500**) use a legacy envelope: `success` stays `true` and the failure is in `data.success: false` with `data.message`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterCreateRequest"
            examples:
              name_only:
                summary: Name only
                description: Creates a channel without description or picture.
                value:
                  name: Product Updates
              with_picture_url:
                summary: Name, description and picture URL
                description: The picture is downloaded, converted to a JPEG of at most 640x640 and uploaded with the channel.
                value:
                  name: Product Updates
                  description: Release notes and product news
                  picture: https://example.com/images/channel-cover.png
              with_picture_data_url:
                summary: Picture as a base64 data URL
                description: Same as the URL variant, with the image embedded in the request (1x1 PNG shown).
                value:
                  name: Product Updates
                  description: Release notes and product news
                  picture: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
      responses:
        "200":
          description: Channel created; `data.jid` is the new channel JID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterCreateResponse"
              example:
                code: 200
                data:
                  success: true
                  jid: 120363000000000022@newsletter
                  name: Product Updates
                  message: Newsletter created successfully
                success: true
        "400":
          description: "Bad Request: invalid body or missing `name` (error envelope), or an unusable `picture` (legacy envelope with `success: true`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterLegacyErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not valid JSON or exceeds 24 MiB
                  value:
                    code: 400
                    error: invalid newsletter JSON (maximum 24 MiB)
                    success: false
                several_objects:
                  summary: More than one JSON value in the body
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                name_required:
                  summary: name missing or blank
                  value:
                    code: 400
                    error: name is required
                    success: false
                unsupported_picture_format:
                  summary: Picture is not JPEG, PNG, GIF or WebP (legacy envelope)
                  value:
                    code: 400
                    data:
                      success: false
                      message: "Invalid image format. Supported: JPEG, PNG, GIF, WebP"
                    success: true
                invalid_picture_input:
                  summary: Picture is neither a data URL nor an HTTP(S) URL (legacy envelope)
                  value:
                    code: 400
                    data:
                      success: false
                      message: "Error processing image: invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user (error envelope), or WhatsApp refused the creation (legacy envelope with `success: true`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterLegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                creation_refused:
                  summary: WhatsApp refused the creation (legacy envelope)
                  value:
                    code: 500
                    data:
                      success: false
                      message: "Error creating newsletter: info query timed out"
                    success: true
  /newsletter/subscribed:
    get:
      tags:
        - Newsletter
      summary: List followed channels (simplified)
      description: |
        Returns the channels this account follows or owns as a flat list with the most useful fields: JID, name,
        description, subscriber count, mute state and invite code.

        **Rules:**
        - `data.newsletters` is **omitted** (not an empty array) when the account follows no channel.
        - `muted` is `true` when this account muted the channel notifications.
        - `description` and `invite_code` are empty strings when WhatsApp does not return them.
        - A failure reported by WhatsApp uses a legacy envelope: `success` stays `true` and the failure is in `data.success: false` with `data.message`.
        - For the raw WhatsApp metadata (state, verification, pictures, role) use `GET /newsletter/list`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Followed channels in simplified form.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterSubscribedResponse"
              examples:
                with_channels:
                  summary: Account follows two channels
                  value:
                    code: 200
                    data:
                      success: true
                      newsletters:
                        - jid: 120363000000000022@newsletter
                          name: Product Updates
                          description: Release notes and product news
                          subscribers: 1500
                          muted: false
                          invite_code: 0029VaExampleInviteCode01
                        - jid: 120363000000000033@newsletter
                          name: Weekly Digest
                          description: ""
                          subscribers: 320
                          muted: true
                          invite_code: 0029VaExampleInviteCode02
                    success: true
                no_channels:
                  summary: Account follows no channel
                  value:
                    code: 200
                    data:
                      success: true
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user (error envelope), or WhatsApp failed to return the list (legacy envelope with `success: true`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterLegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                list_failed:
                  summary: WhatsApp query failed (legacy envelope)
                  value:
                    code: 500
                    data:
                      success: false
                      message: "Error listing newsletters: websocket not connected"
                    success: true
  /newsletter/info:
    post:
      tags:
        - Newsletter
      summary: Get channel information by JID
      description: |
        Fetches the metadata of one channel by its JID: name, description, subscriber count, invite code and, when
        WhatsApp returns viewer data, this account's mute state and role. The account does not need to follow the channel.

        **Rules:**
        - The channel JID is required and must be `<digits>@newsletter` without device. Send it as `NewsletterJID`, `newsletter_jid`, `jid`, `NewsletterID` or `newsletterID`, in the JSON body or in the query string.
        - When the JID is sent under several keys the values must be identical, otherwise the request fails with `conflicting newsletter parameters`.
        - The body must be a single JSON object of at most 1 MiB; keys other than the JID aliases are ignored.
        - `description`, `invite_code` and `role` are omitted when empty; `role` is `owner`, `admin`, `subscriber` or `guest`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NewsletterTargetRequest"
            examples:
              dashboard_field:
                summary: Channel JID in the dashboard SDK field
                description: "`NewsletterJID` is the field sent by the bundled dashboard SDK."
                value:
                  NewsletterJID: 120363000000000022@newsletter
              short_alias:
                summary: Channel JID with the short alias
                description: "`jid` and `newsletter_jid` are accepted as aliases of `NewsletterJID`."
                value:
                  jid: 120363000000000022@newsletter
      responses:
        "200":
          description: Channel metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterInfoResponse"
              example:
                code: 200
                data:
                  jid: 120363000000000022@newsletter
                  name: Product Updates
                  description: Release notes and product news
                  subscribers: 1500
                  invite_code: 0029VaExampleInviteCode01
                  muted: false
                  role: owner
                success: true
        "400":
          description: "Bad Request: the body is not a single JSON object, or the channel JID is missing, invalid or conflicting."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: JID missing or not a newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
                conflicting_jid:
                  summary: Different JIDs under two aliases
                  value:
                    code: 400
                    error: conflicting newsletter parameters
                    success: false
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid newsletter JSON payload
                    success: false
                non_string_jid:
                  summary: JID sent as a number
                  value:
                    code: 400
                    error: newsletter target must be a string
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: WhatsApp returned no channel for this JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_found:
                  summary: No channel returned
                  value:
                    code: 404
                    error: newsletter not found
                    success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp failed to return the channel metadata."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                info_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "error getting info: info query timed out"
                    success: false
  /newsletter/send:
    post:
      tags:
        - Newsletter
      summary: Publish a post to a channel
      description: |
        Publishes a text, media or poll post to a channel this account owns or administers. Channel media is uploaded
        unencrypted and sent with the media handle, as the official client does.

        **Features:**
        - **Text:** `message`.
        - **Media:** `image`, `video`, `document`, `audio` or `sticker`, each as a base64 data URL or an HTTP(S) URL. `caption` applies to image, video and document.
        - **Poll:** `question` with at least 2 `options`; `max_answer` sets how many options a follower may pick.

        **Rules:**
        - `jid` is required and must be an `@newsletter` JID.
        - Send one content type. When several are present, the first non-empty one wins in this order: `message`, `image`, `video`, `document`, `audio`, `sticker`, poll.
        - Audio is always sent as a voice note (PTT) with a generated waveform; stickers are uploaded as images; `filename` (document name and title) defaults to `document`.
        - `max_answer` defaults to 1, values below 1 become 1, and a value above the number of options returns **400**. A `question` with fewer than 2 options counts as no content.
        - The whole request (download, upload and send) times out after 120 seconds.
        - `data.id` is the message ID. Reactions and view receipts need the numeric `server_id` from `POST /newsletter/messages`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterSendRequest"
            examples:
              text:
                summary: Text post
                description: Publishes a plain text post.
                value:
                  jid: 120363000000000022@newsletter
                  message: Version 2.4 is live. Check the release notes on our website.
              image_url:
                summary: Image with caption (URL)
                description: The image is downloaded from the URL, uploaded to the channel media server and sent with a 72x72 thumbnail.
                value:
                  jid: 120363000000000022@newsletter
                  image: https://example.com/images/release-2-4.jpg
                  caption: Version 2.4 highlights
              document_url:
                summary: Document with file name
                description: "`filename` is used as the document name and title shown to followers."
                value:
                  jid: 120363000000000022@newsletter
                  document: https://example.com/files/release-notes-2-4.pdf
                  filename: release-notes-2-4.pdf
                  caption: Full changelog
              voice_note:
                summary: Voice note
                description: Audio is always delivered as a voice note (PTT).
                value:
                  jid: 120363000000000022@newsletter
                  audio: https://example.com/audio/weekly-briefing.ogg
              poll:
                summary: Single-choice poll
                description: A poll needs `question` and at least 2 `options`; `max_answer` 1 makes it single choice.
                value:
                  jid: 120363000000000022@newsletter
                  question: Which feature should ship next?
                  options:
                    - Dark mode
                    - Offline sync
                    - Shared folders
                  max_answer: 1
      responses:
        "200":
          description: Post published.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterSendResponse"
              example:
                code: 200
                data:
                  success: true
                  id: 3EB0C767D26A1D6B8A9F
                  timestamp: 1757901234
                success: true
        "400":
          description: "Bad Request: invalid JSON or JID, no content, unusable media input, or `max_answer` above the number of options."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid JSON
                    success: false
                invalid_jid:
                  summary: jid is missing or not an @newsletter JID
                  value:
                    code: 400
                    error: invalid JID
                    success: false
                no_content:
                  summary: No text, media or valid poll in the body
                  value:
                    code: 400
                    error: no message content provided
                    success: false
                invalid_media:
                  summary: Media is neither a data URL nor an HTTP(S) URL
                  value:
                    code: 400
                    error: "error processing image: invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                max_answer_too_high:
                  summary: max_answer greater than the number of options
                  value:
                    code: 400
                    error: max_answer cannot be greater than options length (2)
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, media upload failure, or WhatsApp rejected the post."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Media upload failed
                  value:
                    code: 500
                    error: "error uploading video: websocket not connected"
                    success: false
                send_failed:
                  summary: WhatsApp rejected the post
                  value:
                    code: 500
                    error: "error sending message: websocket not connected"
                    success: false
  /newsletter/follow:
    post:
      tags:
        - Newsletter
      summary: Follow a channel by JID
      description: |
        Follows (subscribes to) a WhatsApp channel identified by its JID, so its posts appear in this account's Updates tab.
        Use `POST /newsletter/follow-invite` when you only have an invite code or link.

        **Rules:**
        - The channel JID is required (`<digits>@newsletter`, no device) under `NewsletterJID`, `newsletter_jid`, `jid`, `NewsletterID` or `newsletterID`, in the body or the query string; repeated values must match.
        - The body must be a single JSON object of at most 1 MiB. Other keys are ignored.
        - Following a channel that is already followed is passed to WhatsApp as is; the gateway does not check the current state.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NewsletterTargetRequest"
            examples:
              dashboard_field:
                summary: Follow with the dashboard SDK field
                description: "`NewsletterJID` is the field sent by the bundled dashboard SDK."
                value:
                  NewsletterJID: 120363000000000022@newsletter
              snake_case_alias:
                summary: Follow with the snake_case alias
                description: "`newsletter_jid` is equivalent to `NewsletterJID`; sending both with different values returns 400."
                value:
                  newsletter_jid: 120363000000000022@newsletter
      responses:
        "200":
          description: WhatsApp accepted the follow request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Subscribed successfully
                success: true
        "400":
          description: "Bad Request: the body is not a single JSON object, or the channel JID is missing, invalid or conflicting."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: JID missing or not a newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
                conflicting_jid:
                  summary: Different JIDs under two aliases
                  value:
                    code: 400
                    error: conflicting newsletter parameters
                    success: false
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid newsletter JSON payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the follow request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                follow_failed:
                  summary: WhatsApp rejected the request
                  value:
                    code: 500
                    error: "error subscribing: info query timed out"
                    success: false
  /newsletter/follow-invite:
    post:
      tags:
        - Newsletter
      summary: Follow a channel by invite code or link
      description: |
        Follows a WhatsApp channel from its invite code or `https://whatsapp.com/channel/...` link. The gateway first
        resolves the code to the channel metadata, then follows the resolved JID, which is returned in `data.jid`.

        **Rules:**
        - The invite is required under `Code`, `code`, `invite_code`, `NewsletterInviteCode` or `newsletterInviteCode`, in the body or the query string; repeated values must match.
        - Send either the bare code or the full channel link (the `https://whatsapp.com/channel/` prefix is stripped); maximum 2048 bytes.
        - The body must be a single JSON object of at most 1 MiB. Other keys are ignored.
        - An unknown or revoked code fails while resolving it (`error getting info: ...`), before any follow request is sent.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NewsletterTargetRequest"
            examples:
              invite_code:
                summary: Follow with the bare invite code
                description: "`Code` is the field sent by the bundled dashboard SDK."
                value:
                  Code: 0029VaExampleInviteCode01
              channel_link:
                summary: Follow with the full channel link
                description: The link prefix is removed before the code is resolved; the lowercase `code` alias is accepted.
                value:
                  code: https://whatsapp.com/channel/0029VaExampleInviteCode01
      responses:
        "200":
          description: Invite resolved and follow request accepted; `data.jid` is the followed channel.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterFollowInviteResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Subscribed successfully
                  jid: 120363000000000022@newsletter
                success: true
        "400":
          description: "Bad Request: the body is not a single JSON object, or the invite code is missing, too long or conflicting."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_code:
                  summary: Invite code missing, blank or over 2048 bytes
                  value:
                    code: 400
                    error: newsletter invite code is required (maximum 2048 bytes)
                    success: false
                conflicting_code:
                  summary: Different codes under two aliases
                  value:
                    code: 400
                    error: conflicting newsletter parameters
                    success: false
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid newsletter JSON payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: WhatsApp returned no channel for this invite code."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_found:
                  summary: No channel returned
                  value:
                    code: 404
                    error: newsletter not found for invite
                    success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the invite could not be resolved, or WhatsApp rejected the follow request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                resolve_failed:
                  summary: Invite code could not be resolved
                  value:
                    code: 500
                    error: "error getting info: info query timed out"
                    success: false
                follow_failed:
                  summary: WhatsApp rejected the follow request
                  value:
                    code: 500
                    error: "error subscribing: websocket not connected"
                    success: false
  /newsletter/unfollow:
    post:
      tags:
        - Newsletter
      summary: Unfollow a channel by JID
      description: |
        Unfollows (unsubscribes from) a WhatsApp channel identified by its JID, removing it from this account's followed channels.
        To remove a channel you own for everyone, use `DELETE /newsletter/{newsletterID}` instead.

        **Rules:**
        - Send the channel JID (`<digits>@newsletter`, no device) as `NewsletterJID`, `newsletter_jid`, `jid`, `NewsletterID` or `newsletterID`, in the body or the query string. Values under several keys must be identical.
        - Only the JID is read; the body must be one JSON object (max 1 MiB).
        - Use `POST /newsletter/unfollow-invite` when you only have the invite link.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NewsletterTargetRequest"
            examples:
              dashboard_field:
                summary: Unfollow with the dashboard SDK field
                description: "`NewsletterJID` is the field sent by the bundled dashboard SDK."
                value:
                  NewsletterJID: 120363000000000022@newsletter
              short_alias:
                summary: Unfollow with the short alias
                description: "`jid` is accepted as an alias of `NewsletterJID`."
                value:
                  jid: 120363000000000022@newsletter
      responses:
        "200":
          description: WhatsApp accepted the unfollow request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Unsubscribed successfully
                success: true
        "400":
          description: "Bad Request: the body is not a single JSON object, or the channel JID is missing, invalid or conflicting."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: JID missing or not a newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
                conflicting_jid:
                  summary: Different JIDs under two aliases
                  value:
                    code: 400
                    error: conflicting newsletter parameters
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the unfollow request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                unfollow_failed:
                  summary: WhatsApp rejected the request
                  value:
                    code: 500
                    error: "error unsubscribing: info query timed out"
                    success: false
  /newsletter/unfollow-invite:
    post:
      tags:
        - Newsletter
      summary: Unfollow a channel by invite code or link
      description: |
        Unfollows a WhatsApp channel from its invite code or channel link. The gateway resolves the code to the channel
        JID first and then sends the unfollow request for that JID.

        **Rules:**
        - Send the invite as `Code`, `code`, `invite_code`, `NewsletterInviteCode` or `newsletterInviteCode` (body or query string); identical values are required when repeated.
        - The bare code and the `https://whatsapp.com/channel/<code>` link are both accepted, up to 2048 bytes.
        - Unlike `POST /newsletter/follow-invite`, the response does not include the resolved JID.
        - An unknown or revoked code fails while resolving it (`error getting info: ...`) and nothing is unfollowed.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NewsletterTargetRequest"
            examples:
              invite_code:
                summary: Unfollow with the bare invite code
                description: "`Code` is the field sent by the bundled dashboard SDK."
                value:
                  Code: 0029VaExampleInviteCode01
              channel_link:
                summary: Unfollow with the full channel link
                description: "`invite_code` is accepted as an alias; the link prefix is removed before resolving."
                value:
                  invite_code: https://whatsapp.com/channel/0029VaExampleInviteCode01
      responses:
        "200":
          description: Invite resolved and unfollow request accepted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Unsubscribed successfully
                success: true
        "400":
          description: "Bad Request: the body is not a single JSON object, or the invite code is missing, too long or conflicting."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_code:
                  summary: Invite code missing, blank or over 2048 bytes
                  value:
                    code: 400
                    error: newsletter invite code is required (maximum 2048 bytes)
                    success: false
                non_string_code:
                  summary: Invite code sent as a number
                  value:
                    code: 400
                    error: newsletter target must be a string
                    success: false
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid newsletter JSON payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: WhatsApp returned no channel for this invite code."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_found:
                  summary: No channel returned
                  value:
                    code: 404
                    error: newsletter not found for invite
                    success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the invite could not be resolved, or WhatsApp rejected the unfollow request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                resolve_failed:
                  summary: Invite code could not be resolved
                  value:
                    code: 500
                    error: "error getting info: info query timed out"
                    success: false
                unfollow_failed:
                  summary: WhatsApp rejected the unfollow request
                  value:
                    code: 500
                    error: "error unsubscribing: websocket not connected"
                    success: false
  /newsletter/mute:
    post:
      tags:
        - Newsletter
      summary: Mute or unmute channel notifications
      description: |
        Mutes or unmutes the notifications of a channel this account follows. Muting does not unfollow the channel; posts
        keep arriving silently.

        **Rules:**
        - Channel JID required (`<digits>@newsletter`, no device) under `jid`, `NewsletterJID`, `newsletter_jid`, `NewsletterID` or `newsletterID`; the query string is accepted too.
        - `mute` (alias `Mute`) must be a JSON boolean: `true` mutes, `false` unmutes. Strings such as `"true"` are rejected. Omitted or `null` means `false` (unmute), as in the original contract.
        - When both `mute` and `Mute` are sent, `mute` wins.
        - The body must be a JSON object of at most 1 MiB; `null` values count as absent and data after the object is ignored.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterMuteRequest"
            examples:
              mute:
                summary: Mute a channel
                description: Stops notifications for new posts of the channel.
                value:
                  jid: 120363000000000022@newsletter
                  mute: true
              unmute:
                summary: Unmute a channel
                description: Turns the channel notifications back on.
                value:
                  jid: 120363000000000022@newsletter
                  mute: false
      responses:
        "200":
          description: WhatsApp applied the mute setting. `data.message` says `muted` or `enabled`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              examples:
                muted:
                  summary: Channel muted
                  value:
                    code: 200
                    data:
                      success: true
                      message: Notifications muted successfully
                    success: true
                unmuted:
                  summary: Channel unmuted
                  value:
                    code: 200
                    data:
                      success: true
                      message: Notifications enabled successfully
                    success: true
        "400":
          description: "Bad Request: invalid body, invalid or conflicting channel JID, or `mute` not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                mute_not_boolean:
                  summary: mute is not a JSON boolean
                  value:
                    code: 400
                    error: mute must be a boolean
                    success: false
                invalid_jid:
                  summary: JID missing or not a newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the mute change."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                mute_failed:
                  summary: WhatsApp rejected the change
                  value:
                    code: 500
                    error: "error changing mute: info query timed out"
                    success: false
  /newsletter/messages:
    post:
      tags:
        - Newsletter
      summary: List recent posts of a channel
      description: |
        Returns recent posts of a channel with their view and reaction counts, text or caption and media details. Each
        item's `server_id` is the ID required by `POST /newsletter/reaction` and `POST /newsletter/mark-viewed`.

        **Pagination:**
        - `limit` (aliases `count`, `Count`): 1-1000 posts, default 25.
        - `before_id` (aliases `before`, `Before`): only posts older than this server ID. Use the smallest `server_id` of the previous page.
        - Both accept a JSON number or a numeric string.

        **Rules:**
        - The channel JID is required under `NewsletterJID`, `newsletter_jid`, `jid`, `NewsletterID` or `newsletterID`, in the body or the query string; values repeated under several keys must be identical.
        - The body must be a single JSON object of at most 1 MiB.
        - `media` holds `type`, `mimetype`, `url`, `sha256` (base64) and `size`, plus `title` for documents and `duration` (seconds) for audio.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NewsletterTargetRequest"
            examples:
              latest_page:
                summary: Latest 25 posts
                description: Without pagination fields the newest 25 posts are returned.
                value:
                  NewsletterJID: 120363000000000022@newsletter
              older_page:
                summary: Next page with the dashboard fields
                description: Returns 10 posts older than server ID 120.
                value:
                  newsletter_jid: 120363000000000022@newsletter
                  limit: 10
                  before_id: "120"
              legacy_aliases:
                summary: Pagination with count and before aliases
                description: "`count` and `before` are accepted as aliases of `limit` and `before_id`."
                value:
                  jid: 120363000000000022@newsletter
                  count: 50
                  before: 120
      responses:
        "200":
          description: Channel posts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterMessagesResponse"
              example:
                code: 200
                data:
                  success: true
                  messages:
                    - id: 3EB0C767D26A1D6B8A9F
                      server_id: 121
                      type: text
                      timestamp: 1757901234
                      text: Version 2.4 is live
                      views_count: 42
                      reaction_counts:
                        "👍": 3
                    - id: 3EB0C767D26A1D6B8A9E
                      server_id: 120
                      type: media
                      timestamp: 1757897634
                      caption: Version 2.4 highlights
                      views_count: 57
                      media:
                        type: image
                        mimetype: image/jpeg
                        url: https://mmg.whatsapp.net/v/t62.7118-24/example-post-image.jpg
                        sha256: n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
                        size: 48213
                success: true
        "400":
          description: "Bad Request: invalid body, missing or conflicting channel JID, or invalid pagination."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: JID missing or not a newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
                count_out_of_range:
                  summary: limit outside 1-1000
                  value:
                    code: 400
                    error: newsletter count must be between 1 and 1000
                    success: false
                invalid_before:
                  summary: before_id is negative or not numeric
                  value:
                    code: 400
                    error: before must be a nonnegative server message ID
                    success: false
                wrong_type:
                  summary: limit sent as a boolean
                  value:
                    code: 400
                    error: newsletter parameter must be string or integer
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp failed to return the posts."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                fetch_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "error getting messages: info query timed out"
                    success: false
  /newsletter/reaction:
    post:
      tags:
        - Newsletter
      summary: React to a channel post
      description: |
        Sends a reaction from this account to a channel post, or removes the current reaction with an empty `reaction`.

        **Rules:**
        - `jid` (aliases `NewsletterJID`, `newsletter_jid`) must be an `@newsletter` JID.
        - `server_id` (alias `message_id`) is required: a positive numeric server ID as a number or string. Take it from `server_id` in `POST /newsletter/messages`; the `id` returned by `POST /newsletter/send` is not a server ID.
        - The session is checked first (**500** `no session`). As in the original contract, a missing or non-positive numeric `server_id` or a parseable JID that is not an `@newsletter` JID (for example `{}`) returns **500** `error sending reaction: ...`; nothing is sent to WhatsApp. A body that is not a JSON object, a field with the wrong type or a non-numeric string ID returns **400**.
        - `reaction` (alias `emoji`): the emoji to set; `""`, `null` or omitted removes the reaction (original contract).
        - The body must be a JSON object of at most 1 MiB. Unknown keys are ignored, `null` values count as absent, key names are case-insensitive, and the canonical key wins over a differing alias.
        - Reacting is a follower action: when the channel owner reacts to its own post the call returns 200 but no reaction becomes visible.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterReactionRequest"
            examples:
              add_reaction:
                summary: React with an emoji
                description: Adds or replaces this account's reaction on post 121.
                value:
                  jid: 120363000000000022@newsletter
                  server_id: 121
                  reaction: "👍"
              remove_reaction:
                summary: Remove the reaction
                description: An empty `reaction` removes this account's reaction.
                value:
                  jid: 120363000000000022@newsletter
                  server_id: 121
                  reaction: ""
              aliases:
                summary: Dashboard aliases
                description: "`NewsletterJID`, `message_id` (numeric string) and `emoji` are normalized to `jid`, `server_id` and `reaction`."
                value:
                  NewsletterJID: 120363000000000022@newsletter
                  message_id: "121"
                  emoji: "❤️"
      responses:
        "200":
          description: WhatsApp accepted the reaction change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Reaction sent successfully
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object, a field has the wrong type (including a non-numeric string `server_id`), or the JID cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid newsletter JSON object
                    success: false
                invalid_type:
                  summary: A known field has the wrong JSON type
                  value:
                    code: 400
                    error: invalid newsletter parameter type
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, `server_id` missing or not positive, `jid` missing or not a newsletter JID (original contract), or WhatsApp rejected the reaction."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                invalid_server_id:
                  summary: server_id missing, zero or negative
                  value:
                    code: 500
                    error: "error sending reaction: message ID must be a positive numeric server ID"
                    success: false
                invalid_jid:
                  summary: jid missing or not a newsletter JID
                  value:
                    code: 500
                    error: "error sending reaction: invalid newsletter JID"
                    success: false
                reaction_failed:
                  summary: WhatsApp rejected the reaction
                  value:
                    code: 500
                    error: "error sending reaction: websocket not connected"
                    success: false
  /newsletter/mark-viewed:
    post:
      tags:
        - Newsletter
      summary: Mark channel posts as viewed
      description: |
        Sends a view receipt for one or more channel posts, incrementing their `views_count`. This is not the same as
        marking the chat as read on other devices.

        **Rules:**
        - `jid` (aliases `NewsletterJID`, `newsletter_jid`) must be an `@newsletter` JID.
        - `server_ids` (alias `message_ids`) is required: at least one positive server ID (numbers or numeric strings) from `POST /newsletter/messages`.
        - The session is checked first (**500** `no session`). As in the original contract, missing or empty `server_ids`, a non-positive numeric ID or a parseable JID that is not an `@newsletter` JID (for example `{}`) returns **500** `error marking as viewed: ...`; nothing is sent to WhatsApp. A body that is not a JSON object, a field with the wrong type or a non-numeric string ID returns **400**.
        - The body must be a JSON object of at most 1 MiB. Unknown keys are ignored and `null` values count as absent.
        - A view is counted only for posts this account has not viewed yet; repeating the call returns 200 without changing `views_count`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterMarkViewedRequest"
            examples:
              server_ids:
                summary: Mark two posts as viewed
                description: Sends one view receipt covering posts 120 and 121.
                value:
                  jid: 120363000000000022@newsletter
                  server_ids:
                    - 120
                    - 121
              aliases:
                summary: Dashboard aliases with string IDs
                description: "`newsletter_jid` and `message_ids` are normalized; numeric strings are converted to numbers."
                value:
                  newsletter_jid: 120363000000000022@newsletter
                  message_ids:
                    - "120"
                    - "121"
      responses:
        "200":
          description: WhatsApp accepted the view receipt.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Messages marked as viewed
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object, a field has the wrong type (including non-numeric string IDs), or the JID cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid newsletter JSON object
                    success: false
                invalid_type:
                  summary: A known field has the wrong JSON type
                  value:
                    code: 400
                    error: invalid newsletter parameter type
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, `server_ids` missing, empty or not positive, `jid` missing or not a newsletter JID (original contract), or WhatsApp rejected the view receipt."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                ids_required:
                  summary: server_ids missing or empty
                  value:
                    code: 500
                    error: "error marking as viewed: provide at least one server message ID"
                    success: false
                invalid_id:
                  summary: An ID is zero or negative
                  value:
                    code: 500
                    error: "error marking as viewed: message ID must be a positive numeric server ID"
                    success: false
                receipt_failed:
                  summary: WhatsApp rejected the receipt
                  value:
                    code: 500
                    error: "error marking as viewed: websocket not connected"
                    success: false
  /newsletter/subscribe-live-updates:
    post:
      tags:
        - Newsletter
      summary: Subscribe to live reaction and view updates of a channel
      description: |
        Asks WhatsApp to push live reaction and view-count updates of a channel to this session for a limited time.
        WhatsApp answers with the subscription length in seconds (`data.duration`); renew the subscription before it expires.

        **Rules:**
        - Target channel: `NewsletterJID`, `newsletter_jid`, `jid`, `NewsletterID` or `newsletterID` (body or query string), formatted `<digits>@newsletter` without device; conflicting values under several keys return 400.
        - The body must be a single JSON object of at most 1 MiB; keys other than the channel aliases are ignored.
        - The WhatsApp request has its own 30-second timeout.
        - `duration` is `0` when WhatsApp does not state a length.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterLiveUpdatesRequest"
            examples:
              subscribe:
                summary: Subscribe to live updates
                description: Starts (or renews) the live updates subscription of the channel.
                value:
                  jid: 120363000000000022@newsletter
              dashboard_field:
                summary: Subscribe with the dashboard SDK field
                description: "`NewsletterJID` is accepted as an alias of `jid`."
                value:
                  NewsletterJID: 120363000000000022@newsletter
      responses:
        "200":
          description: Subscription accepted; `data.duration` is its length in seconds.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterLiveUpdatesResponse"
              example:
                code: 200
                data:
                  success: true
                  duration: 300
                  message: Subscribed to live updates
                success: true
        "400":
          description: "Bad Request: the body is not a single JSON object, or the channel JID is missing, invalid or conflicting."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: JID missing or not a newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
                json_object_required:
                  summary: Body is JSON null
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp did not accept the subscription within 30 seconds."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                subscribe_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "error subscribing to live updates: info query timed out"
                    success: false
  /newsletter/message-updates:
    post:
      tags:
        - Newsletter
      summary: Get reaction and view-count updates of channel posts
      description: |
        Returns the latest reaction and view counts of channel posts, like the official client's message-updates query.
        The request always carries a cursor: `after`, `before`, or (when neither is sent) a cursor anchored on the newest post.

        **Cursor:**
        - `after`: updates for posts newer than this server ID.
        - `before`: updates for posts older than this server ID (exclusive). When `after` is also sent, `after` wins.
        - Neither: the gateway reads the newest post first and asks for the latest `count` posts; a channel without posts returns an empty list.
        - `count`: `0`, negative or omitted means 25; other values are passed to WhatsApp unchanged (original contract). `since` (Unix seconds) limits the result to changes after that time; negative cursors and `since` are ignored.

        **Rules:**
        - `jid` (aliases `NewsletterJID`, `newsletter_jid`) must be an `@newsletter` JID.
        - The body must be a JSON object of at most 1 MiB. Unknown keys (including the unsupported `message_ids`) are ignored and `null` values count as absent.
        - `media` in each update only carries `type`, `mimetype`, `url` (plus `title` for documents), and only for image, video and document posts.
        - WhatsApp may not answer this query; the call then fails with **500** `error getting updates: info query timed out`.
        - The session is checked first (**500** `no session`). As in the original contract, a missing `jid` or a parseable JID that is not an `@newsletter` JID (for example `{}`) returns **500** `error getting updates: invalid newsletter JID`; nothing is sent to WhatsApp. A body that is not a JSON object, a field with the wrong type or an unparseable JID returns **400**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterMessageUpdatesRequest"
            examples:
              latest:
                summary: Updates of the latest 25 posts
                description: No cursor; the gateway anchors on the newest post.
                value:
                  jid: 120363000000000022@newsletter
              after_cursor:
                summary: Updates for posts newer than a server ID
                description: Returns up to 50 updates for posts newer than server ID 120.
                value:
                  jid: 120363000000000022@newsletter
                  count: 50
                  after: 120
              before_since:
                summary: Older posts changed since a time
                description: Posts older than server ID 121 whose counters changed after the given Unix time.
                value:
                  jid: 120363000000000022@newsletter
                  before: 121
                  since: 1757890000
      responses:
        "200":
          description: Updates of the selected posts; `data.updates` is empty when nothing matches.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterMessageUpdatesResponse"
              example:
                code: 200
                data:
                  success: true
                  updates:
                    - id: 3EB0C767D26A1D6B8A9F
                      server_id: 121
                      type: text
                      timestamp: 1757901234
                      views_count: 58
                      reaction_counts:
                        "👍": 5
                        "❤️": 2
                      text: Version 2.4 is live
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object, a field has the wrong type, or the JID cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_type:
                  summary: A known field has the wrong JSON type
                  value:
                    code: 400
                    error: invalid newsletter parameter type
                    success: false
                invalid_json:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid newsletter JSON object
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, `jid` missing or not a newsletter JID (original contract), the newest post could not be read, or WhatsApp did not answer the updates query."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                invalid_jid:
                  summary: jid missing or not a newsletter JID
                  value:
                    code: 500
                    error: "error getting updates: invalid newsletter JID"
                    success: false
                query_timeout:
                  summary: WhatsApp did not answer
                  value:
                    code: 500
                    error: "error getting updates: info query timed out"
                    success: false
                anchor_failed:
                  summary: Newest post could not be read
                  value:
                    code: 500
                    error: "error getting updates: could not read the newest post to anchor the updates cursor: websocket not connected"
                    success: false
        "501":
          description: "Not Implemented: the bundled WhatsApp library lacks the before-cursor updates query. The library shipped with this build implements it, so this is a defensive answer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 501
                error: "error getting updates: the bundled WhatsApp library does not support this official newsletter operation yet"
                success: false
  /newsletter/accept-tos:
    post:
      tags:
        - Newsletter
      summary: Accept a WhatsApp terms of service notice
      description: |
        Accepts a WhatsApp terms of service notice for this account. Accepting the channel notice is required once before
        `POST /newsletter/create`; the defaults target that notice.

        **Rules:**
        - A JSON object is required: send `{}` to accept the channel notice with the defaults. An empty body returns **400** `invalid JSON`.
        - `notice_id` defaults to `20601218` and `stage` to `5` when omitted or empty. Both must be JSON strings; numbers are rejected as `invalid JSON`.
        - Unknown keys are ignored.
        - The session is checked before the body, so a missing session returns **500** even for an invalid body.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterAcceptTOSRequest"
            examples:
              channel_defaults:
                summary: Accept the channel creation notice
                description: An empty object uses notice `20601218`, stage `5`.
                value: {}
              explicit_notice:
                summary: Explicit notice and stage
                description: Same notice as the defaults, sent explicitly.
                value:
                  notice_id: "20601218"
                  stage: "5"
      responses:
        "200":
          description: WhatsApp accepted the notice.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterActionResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Terms of service accepted
                success: true
        "400":
          description: "Bad Request: the body is empty, not valid JSON, or has non-string fields."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: invalid JSON
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the acceptance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                accept_failed:
                  summary: WhatsApp rejected the request
                  value:
                    code: 500
                    error: "error accepting terms: websocket not connected"
                    success: false
  /newsletter/admin-invite:
    post:
      tags:
        - Newsletter
      summary: Invite a contact to become a channel admin
      description: |
        Invites a person to become admin of a channel this account owns, following the official client flow: the gateway
        first creates the invite on the WhatsApp server for the invitee's LID (the server returns the expiration) and only
        then sends the `NewsletterAdminInviteMessage` carrying that expiration to the invitee.

        **Rules:**
        - `phone`, `newsletter_jid`, `image`, `caption` and `name` are all required and non-empty.
        - `phone` accepts a phone number, an `@s.whatsapp.net` JID or an `@lid` JID. Groups and other JID types are refused.
        - Phone numbers are resolved to a LID from the local store or a WhatsApp lookup; when no LID is found the call fails with **400** (message the contact first or pass the `@lid` JID).
        - `image` (base64 data URL or HTTP(S) URL) becomes a 96px JPEG thumbnail; an undecodable image is sent without thumbnail. It is fetched before `newsletter_jid` is validated.
        - `name` is the channel name shown in the invite card; the gateway does not read it from the channel.
        - A message sent without a server-side invite shows as "invite cancelled", which is why a refused invite (**502**) sends nothing.

        **Response:** `expires_at` is the server expiration (Unix seconds), `invitee_lid` the resolved LID and `invite_created` is always `true`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterAdminInviteRequest"
            examples:
              by_phone:
                summary: Invite by phone number
                description: The number is resolved to the invitee's LID before the invite is created.
                value:
                  phone: "5511999999999"
                  newsletter_jid: 120363000000000022@newsletter
                  image: https://example.com/images/channel-cover.png
                  caption: Join me as admin of Product Updates
                  name: Product Updates
              by_lid:
                summary: Invite by LID with a data URL image
                description: A `@lid` JID skips the LID lookup (1x1 PNG shown as image).
                value:
                  phone: 123456789012345@lid
                  newsletter_jid: 120363000000000022@newsletter
                  image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
                  caption: Join me as admin of Product Updates
                  name: Product Updates
      responses:
        "200":
          description: Invite created on the server and invite message sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterAdminInviteResponse"
              example:
                code: 200
                data:
                  success: true
                  id: 3EB0A1B2C3D4E5F60718
                  timestamp: 1757901234
                  recipient: "5511999999999"
                  newsletter_jid: 120363000000000022@newsletter
                  invite_name: Product Updates
                  expires_at: 1758506034
                  invitee_lid: 123456789012345@lid
                  invite_created: true
                  message: Newsletter admin invite sent successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, missing fields, unparseable phone, unusable image, invalid channel JID, non-person invitee or unresolvable LID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid JSON
                    success: false
                missing_fields:
                  summary: A required field is empty
                  value:
                    code: 400
                    error: phone, newsletter_jid, image, caption and name are required
                    success: false
                invalid_phone:
                  summary: phone cannot be parsed
                  value:
                    code: 400
                    error: could not parse phone number
                    success: false
                invalid_newsletter_jid:
                  summary: newsletter_jid is not an @newsletter JID
                  value:
                    code: 400
                    error: invalid newsletter JID
                    success: false
                not_a_person:
                  summary: phone is a group or another non-user JID
                  value:
                    code: 400
                    error: admin invites can only be sent to a person
                    success: false
                lid_not_found:
                  summary: Invitee LID could not be resolved
                  value:
                    code: 400
                    error: could not resolve the invitee LID; send a message to this contact first or pass the @lid JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the invite message could not be sent after the server-side invite was created."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Invite message not sent
                  value:
                    code: 500
                    error: "error sending admin invite: websocket not connected"
                    success: false
        "501":
          description: "Not Implemented: the bundled WhatsApp library cannot create admin invites. The library shipped with this build implements it, so this is a defensive answer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 501
                error: the bundled WhatsApp library does not support this official newsletter operation yet
                success: false
        "502":
          description: "Bad Gateway: WhatsApp refused to create the invite (for example, this account is not the channel owner). No message is sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "whatsapp did not create the admin invite: info query timed out"
                success: false
  /health:
    get:
      tags:
        - System
      summary: Check server health
      description: |
        Public liveness and readiness probe. Returns the aggregated health status, runtime metrics and the result of a database ping.

        **Rules:**
        - No authentication.
        - The only dependency checked is the database: a ping with a 250 ms timeout.
        - Ping succeeds within 750 ms: service `up`, overall `healthy`, HTTP **200**.
        - Ping succeeds but takes longer than 750 ms: service `degraded`, overall `degraded`, still HTTP **200**. In practice the 250 ms timeout usually turns a slow database into `down` first.
        - Ping fails, times out, or no database is configured: service `down`, overall `unhealthy`, HTTP **503**.
        - The snapshot is cached for 3 seconds, so faster probes return the same body.

        **Payload:**
        - Health fields sit at the top level next to `code`, `data` and `success`. `data` is `OK` for 200 and `ERROR` for 503; `success` is `false` on 503. There is no `error` field.
        - `build.commit` appears only when `GIT_COMMIT` is set; `environment.app_env` comes from `APP_ENV` or `ENVIRONMENT`, `environment.app_region` from `APP_REGION`.
      security: []
      responses:
        "200":
          description: Server healthy or degraded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatusResponse"
              example:
                code: 200
                data: OK
                success: true
                status: healthy
                version: v1.20.4
                uptime: 86400
                timestamp: "2026-09-15T12:00:00Z"
                metrics:
                  goroutines: 42
                  go_version: go1.25.1
                  cpu_count: 8
                  go_max_procs: 8
                environment:
                  containerized: true
                  hostname: zuckzapgo-01
                  app_env: production
                build:
                  commit: a1b2c3d4
                services:
                  database:
                    status: up
                    latency_ms: 1.42
                    open_connections: 10
                    in_use: 2
                    idle: 8
                    driver: postgres
        "503":
          description: "Service Unavailable: the database ping failed or no database is configured. The body keeps the health payload shape (no `error` field)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatusEnvelope"
              example:
                code: 503
                data: ERROR
                success: false
                status: unhealthy
                version: v1.20.4
                uptime: 3600
                timestamp: "2026-09-15T12:00:00Z"
                metrics:
                  goroutines: 38
                  go_version: go1.25.1
                  cpu_count: 8
                  go_max_procs: 8
                environment:
                  containerized: true
                  hostname: zuckzapgo-01
                services:
                  database:
                    status: down
                    error: context deadline exceeded
                    latency_ms: 250.31
                    open_connections: 0
                    in_use: 0
                    idle: 0
                    driver: postgres
  /webhook:
    get:
      tags:
        - Webhook
      summary: Get the instance webhook and subscribed events
      description: |
        Returns the individual webhook URL of this instance and the event types it is subscribed to.

        **Rules:**
        - `webhook` is empty when no individual webhook is configured.
        - `subscribe` is the stored comma-separated list split into an array. When nothing is stored the array contains one empty string (`[""]`).
        - The server-wide global webhook is configured by environment variables and is not part of this response.

        **Event types:**
        - **Messages:** `Message`, `UndecryptableMessage`, `FBMessage`, `Receipt`, `MediaRetry`, `ReadReceipt`
        - **Chat management:** `Star`, `Pin`, `Archive`, `Mute`, `MarkChatAsRead`, `ClearChat`, `DeleteChat`, `DeleteForMe`, `LabelAssociationChat`, `LabelAssociationMessage`, `LabelEdit`
        - **Groups and contacts:** `GroupInfo`, `JoinedGroup`, `Contact`, `Picture`, `PushName`, `BusinessName`, `UserAbout`
        - **Privacy:** `PrivacySettings`, `IdentityChange`, `Blocklist`, `BlocklistChange`, `UserStatusMute`
        - **Connection:** `Connected`, `Disconnected`, `QR`, `PairSuccess`, `PairError`, `QRScannedWithoutMultidevice`, `LoggedOut`, `StreamReplaced`, `StreamError`, `ConnectFailure`, `KeepAliveTimeout`, `KeepAliveRestored`, `ManualLoginReconnect`, `TemporaryBan`, `ClientOutdated`, `CATRefreshError`, `PasskeyRequest`, `PasskeyConfirmation`
        - **Calls:** `CallOffer`, `CallAccept`, `CallAcceptEngine`, `CallPreAccept`, `CallTransport`, `CallTerminate`, `CallTerminateEngine`, `CallRecordingReady`, `CallReject`, `CallOfferNotice`, `CallRelayLatency`, `CallReminder`, `UnknownCallEvent`
        - **Presence:** `Presence`, `ChatPresence`
        - **Newsletters:** `NewsletterJoin`, `NewsletterLeave`, `NewsletterMuteChange`, `NewsletterLiveUpdate`
        - **Sync:** `AppState`, `AppStateSyncComplete`, `AppStateSyncError`, `HistorySync`, `OfflineSyncPreview`, `OfflineSyncCompleted`, `PushNameSetting`, `UnarchiveChatsSetting`
        - **Special:** `All` subscribes to every event type
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored webhook URL and subscription.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookGetResponse"
              examples:
                configured:
                  summary: Webhook with selected events
                  value:
                    code: 200
                    data:
                      webhook: https://example.com/webhooks/whatsapp
                      subscribe:
                        - Message
                        - ReadReceipt
                        - Connected
                    success: true
                not_configured:
                  summary: No webhook stored
                  value:
                    code: 200
                    data:
                      webhook: ""
                      subscribe:
                        - ""
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the webhook could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "could not get webhook: sql: database is closed"
                success: false
    post:
      tags:
        - Webhook
      summary: Set the instance webhook URL and events
      description: |
        Sets the individual webhook URL of this instance and, optionally, replaces its event subscription.

        **Rules:**
        - `webhookurl` is stored as sent, without URL validation. An empty or omitted value clears the webhook and unregisters the individual webhook transport.
        - `events` omitted or empty: only the URL changes and the stored subscription is kept.
        - `events` non-empty: names are matched exactly (case-sensitive, no trimming), unknown names are silently discarded and the valid ones **replace** the stored subscription. If every name is unknown, the subscription becomes empty.
        - Event names are the ones listed in `GET /webhook`; `All` subscribes to every event type.

        **Side effects:**
        - The user cache is updated, live client subscriptions are refreshed and the individual webhook transport is registered in the event dispatcher.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookSet"
            examples:
              with_events:
                summary: URL and selected events
                description: Stores the URL and subscribes only to messages, read receipts and connection events.
                value:
                  webhookurl: https://example.com/webhooks/whatsapp
                  events:
                    - Message
                    - ReadReceipt
                    - Connected
              all_events:
                summary: URL and every event type
                description: "`All` subscribes the webhook to every supported event type."
                value:
                  webhookurl: https://example.com/webhooks/whatsapp
                  events:
                    - All
              url_only:
                summary: Change only the URL
                description: Without `events`, the stored subscription is kept.
                value:
                  webhookurl: https://example.com/webhooks/whatsapp-v2
              clear:
                summary: Remove the webhook URL
                description: An empty URL unregisters the individual webhook transport; the stored subscription is kept.
                value:
                  webhookurl: ""
      responses:
        "200":
          description: Webhook stored. `data.webhook` echoes the stored URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSetResponse"
              example:
                code: 200
                data:
                  webhook: https://example.com/webhooks/whatsapp
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `events` is not an array of strings."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the webhook could not be stored."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "could not set webhook: sql: database is closed"
                success: false
    put:
      tags:
        - Webhook
      summary: Update the webhook URL, events and activation
      description: |
        Updates the individual webhook of this instance, with an explicit activation switch.

        **Rules:**
        - `active` defaults to `false` when omitted. With `active: false` the stored URL is cleared and the transport unregistered, whatever `webhookurl` says; send `active: true` to keep a webhook.
        - `events` non-empty: names are trimmed, unknown names silently discarded, and the valid list replaces the stored subscription. With `active: false` the subscription is cleared instead.
        - `events` omitted or empty: only the URL column is written and the stored subscription is kept.
        - `webhookurl` is stored without URL validation.

        **Response:**
        - `data.webhook` is the stored URL (empty when inactive), `data.events` the accepted event names (`null` when none was sent or accepted) and `data.active` echoes the request.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookUpdate"
            examples:
              activate:
                summary: Activate with URL and events
                description: Stores the URL, replaces the subscription and registers the webhook transport.
                value:
                  webhookurl: https://example.com/webhooks/whatsapp
                  events:
                    - Message
                    - ReadReceipt
                  active: true
              url_only:
                summary: Change the URL and keep the events
                description: Without `events`, only the URL changes. `active` must be `true` or the URL is cleared.
                value:
                  webhookurl: https://example.com/webhooks/whatsapp-v2
                  active: true
              deactivate:
                summary: Deactivate and clear the subscription
                description: "`active: false` with a non-empty `events` clears both the URL and the stored subscription."
                value:
                  webhookurl: ""
                  events:
                    - Message
                  active: false
      responses:
        "200":
          description: Webhook updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookUpdateResponse"
              examples:
                active:
                  summary: Active webhook
                  value:
                    code: 200
                    data:
                      active: true
                      events:
                        - Message
                        - ReadReceipt
                      webhook: https://example.com/webhooks/whatsapp
                    success: true
                inactive:
                  summary: Deactivated webhook
                  value:
                    code: 200
                    data:
                      active: false
                      events:
                        - Message
                      webhook: ""
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON or a field has the wrong type."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the webhook could not be stored."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "could not update webhook: sql: database is closed"
                success: false
    delete:
      tags:
        - Webhook
      summary: Delete the instance webhook and its events
      description: |
        Removes the individual webhook of this instance: the URL and the event subscription are both cleared.

        **Rules:**
        - No request body.
        - The individual webhook transport is unregistered from the event dispatcher and the user cache is updated.
        - The server-wide global webhook and other transports (RabbitMQ, SQS, Redis, WebSocket) are not affected.
        - Deleting when no webhook is configured still returns **200**.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Webhook URL and events cleared.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeleteResponse"
              example:
                code: 200
                data:
                  Details: Webhook and events deleted successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the webhook could not be removed from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "could not delete webhook: sql: database is closed"
                success: false
  /session/connect:
    post:
      tags:
        - Session
      summary: Connect the instance to WhatsApp
      description: |
        Starts the WhatsApp client of this instance in the background and stores the event subscription used by its individual webhook.

        Without a stored session the client starts in pairing mode: read the code with `GET /session/qr` or request a phone code with `POST /session/pairphone`. A stored, valid session reconnects without scanning. Use this endpoint again after `POST /session/disconnect`.

        **Rules:**
        - The body is required; an empty body returns `400 could not decode Payload`. Send at least `{}`. Field names are matched case-insensitively.
        - `Subscribe` **replaces** the stored subscription (`users.events`) on every call. Unknown names are discarded and duplicates removed; omitting it, sending `[]` or only unknown names stores an empty subscription, which stops individual webhook deliveries (global transports are not affected). Send `["All"]` to receive every event.
        - `Immediate: false` (default) blocks for about 10 seconds and returns `500 failed to connect` when the socket is not up by then. `Immediate: true` returns at once; check `GET /session/status` afterwards.
        - A connected client returns `500 already connected`; a stale, disconnected client is cleaned up and replaced.
        - `data.jid` and `data.webhook` are the values stored before this call (`jid` is empty before the first pairing); `data.events` is the new comma-separated subscription.

        **Event names:** `Message`, `ReadReceipt`, `Receipt`, `Presence`, `ChatPresence`, `GroupInfo`, `Connected`, `Disconnected`, `LoggedOut`, `QR`, `PairSuccess`, `CallOffer`, `CallTerminate`, `NewsletterJoin`, `HistorySync`, `All` and the rest of `supportedEventTypes` in `constants.go`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Connect"
            examples:
              selected_events:
                summary: Subscribe to selected events and return immediately
                description: Stores `Message,ReadReceipt,Connected` as the individual webhook subscription and answers without waiting for the socket.
                value:
                  Subscribe:
                    - Message
                    - ReadReceipt
                    - Connected
                  Immediate: true
              all_events_wait:
                summary: All events, wait up to 10 seconds
                description: Blocks for about 10 seconds and fails with 500 when the client is not connected by then.
                value:
                  Subscribe:
                    - All
                  Immediate: false
              no_subscription:
                summary: Connect without individual webhook events
                description: Omitting `Subscribe` clears the stored subscription; global transports still receive events.
                value:
                  Immediate: true
      responses:
        "200":
          description: Client started. With `Immediate:false` the socket was connected within the wait.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionConnectResponse"
              examples:
                paired_session:
                  summary: Stored session reconnected
                  value:
                    code: 200
                    data:
                      details: Connected!
                      events: Message,ReadReceipt,Connected
                      jid: 5511999999999:12@s.whatsapp.net
                      skip_media_download: false
                      webhook: https://example.com/webhook
                    success: true
                first_pairing:
                  summary: New instance, waiting for QR scan
                  value:
                    code: 200
                    data:
                      details: Connected!
                      events: All
                      jid: ""
                      skip_media_download: false
                      webhook: ""
                    success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the client is already connected, or the socket did not connect within the 10-second wait."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                already_connected:
                  summary: A connected client already exists
                  value:
                    code: 500
                    error: already connected
                    success: false
                failed_to_connect:
                  summary: Socket not connected after the wait
                  value:
                    code: 500
                    error: failed to connect
                    success: false
  /session/disconnect:
    post:
      tags:
        - Session
      summary: Disconnect from WhatsApp and keep the session
      description: |
        Closes the WhatsApp socket of this instance while keeping the paired device credentials, so `POST /session/connect` can resume later without scanning a QR code again.

        **Rules:**
        - No request body is read.
        - Requires a client in memory; otherwise returns `500 no session`.
        - When the client was connected or logged in, a synthetic `Disconnected` event (`source: session/disconnect`, `synthetic: true`) is dispatched to the configured webhooks before cleanup.
        - The client resources are released, the `connected` flag is cleared in the database and any stored QR code is discarded.
        - `wasConnected` / `wasLoggedIn` report the client state immediately before the call.

        **Use Cases:**
        - Pause event delivery during maintenance
        - Apply a new proxy (`POST /session/proxy` requires a disconnected client)
        - Use `POST /session/logout` instead to unlink the device for good
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Client disconnected; the paired session is kept.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionDisconnectResponse"
              example:
                code: 200
                data:
                  Details: Disconnected successfully
                  wasConnected: true
                  wasLoggedIn: true
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for this instance (never connected, or already disconnected)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false
  /session/logout:
    post:
      tags:
        - Session
      summary: Log out and unlink the device
      description: |
        Logs the linked device out of WhatsApp and removes the local session, so the next `POST /session/connect` starts a fresh pairing (QR code or phone code).

        **Rules:**
        - No request body is read.
        - Requires a client in memory; otherwise returns `500 no session`.
        - When the client is logged in, the gateway asks WhatsApp to remove the device (30-second timeout). The local cleanup always runs, even when that request fails; `logoutSuccess: false` tells that WhatsApp did not confirm the removal.
        - A client that was not logged in is only cleaned up and reports `logoutSuccess: true`.
        - The stored QR code is cleared and the `connected` flag is reset.

        **Use Cases:**
        - Revoke API access to a WhatsApp account
        - Switch the instance to another phone number
        - Use `POST /session/disconnect` to pause without unlinking
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Local session removed. Check `logoutSuccess` to know whether WhatsApp confirmed the device removal.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionLogoutResponse"
              examples:
                logged_out:
                  summary: Device removed from WhatsApp
                  value:
                    code: 200
                    data:
                      Details: Logout completed
                      logoutSuccess: true
                      wasConnected: true
                      wasLoggedIn: true
                    success: true
                cleanup_only:
                  summary: WhatsApp did not confirm; local session removed anyway
                  value:
                    code: 200
                    data:
                      Details: Logout completed
                      logoutSuccess: false
                      wasConnected: false
                      wasLoggedIn: true
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false
  /session/refresh:
    post:
      tags:
        - Session
      summary: Reconnect and resynchronize the session
      description: |
        Restarts the WhatsApp client of an already paired instance and, once the socket is back, optionally resynchronizes app state, pre-keys and privacy settings.

        **Features:**
        - **Reconnect:** releases the current client, starts a new one with the stored subscription and waits 3 seconds.
        - **App state:** fetches the five collections `critical_block`, `critical_unblock_low`, `regular_low`, `regular_high` and `regular` from scratch.
        - **Auto recovery:** when a collection fails, asks the primary phone for an app-state recovery snapshot.
        - **Pre-keys:** reads the server pre-key count and uploads new keys when fewer than 5 remain.
        - **Privacy:** fetches the privacy settings bypassing the cache.

        **Rules:**
        - The body is optional; every flag defaults to `true`. An unreadable body is ignored and the defaults are used.
        - The instance must have a stored JID; before the first pairing the call returns `400`.
        - The sync steps only run when the new client is connected after the 3-second wait. `sync` and `sync_options` are omitted otherwise, or when all flags are `false`.
        - When the new client is not connected, the response is **500** but keeps the legacy envelope: `success: true` at the top level and the details in `data` with `refreshSuccess: false`.
        - Typical duration: 4 to 10 seconds. Do not call it in a tight loop.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionRefreshRequest"
            examples:
              full_refresh:
                summary: Full refresh (same as an empty body)
                description: Reconnects and runs every sync step with automatic recovery.
                value:
                  sync_app_state: true
                  refresh_prekeys: true
                  refresh_privacy: true
                  auto_recovery: true
              reconnect_only:
                summary: Reconnect without sync steps
                description: Only restarts the client; `sync` and `sync_options` are omitted from the response.
                value:
                  sync_app_state: false
                  refresh_prekeys: false
                  refresh_privacy: false
              app_state_without_recovery:
                summary: Resync app state without asking the phone for recovery
                description: Runs the app-state fetch only and never sends recovery requests to the primary device.
                value:
                  sync_app_state: true
                  refresh_prekeys: false
                  refresh_privacy: false
                  auto_recovery: false
      responses:
        "200":
          description: Client reconnected; `data.sync` holds the result of each requested step.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRefreshResponse"
              example:
                code: 200
                data:
                  Details: Connection refresh completed
                  refreshSuccess: true
                  wasConnected: true
                  wasLoggedIn: true
                  newConnected: true
                  newLoggedIn: true
                  jid: 5511999999999:12@s.whatsapp.net
                  sync_options:
                    sync_app_state: true
                    refresh_prekeys: true
                    refresh_privacy: true
                    auto_recovery: true
                  sync:
                    app_state:
                      - name: critical_block
                        success: true
                        duration: 1.234s
                      - name: critical_unblock_low
                        success: true
                        duration: 856.1ms
                      - name: regular_low
                        success: true
                        duration: 523.4ms
                      - name: regular_high
                        success: false
                        error: "failed to verify patch v42: mismatching LTHash"
                        duration: 412.9ms
                        recovery_requested: true
                      - name: regular
                        success: true
                        duration: 678.2ms
                    prekeys:
                      checked: true
                      server_count: 47
                    privacy_settings:
                      fetched: true
                    total_duration: 4.521s
                success: true
        "400":
          description: "Bad Request: the instance has never been paired, so there is no JID to reconnect."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: no JID found, QR scan required for initial pairing
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error. When the new client is not connected after the wait, the body keeps the legacy envelope (`success: true`, details in `data`, `data.error` set). A response that cannot be encoded uses the error envelope."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRefreshFailureResponse"
              examples:
                not_reconnected:
                  summary: Client did not reconnect (legacy envelope)
                  value:
                    code: 500
                    data:
                      Details: Connection refresh completed
                      error: Failed to refresh connection
                      refreshSuccess: false
                      wasConnected: true
                      wasLoggedIn: true
                      newConnected: false
                      newLoggedIn: false
                      jid: 5511999999999:12@s.whatsapp.net
                    success: true
                encoding_failed:
                  summary: Response could not be encoded
                  value:
                    code: 500
                    error: "json: unsupported value: NaN"
                    success: false
  /session/status:
    get:
      tags:
        - Session
      summary: Get connection, pairing and configuration status
      description: |
        Returns a snapshot of the instance: socket and login state, connection health, stored configuration (webhook, events, skip flags, proxy, S3, RabbitMQ) and the pending passkey pairing state.

        **Rules:**
        - Always answers 200 for a valid token, even without a client (`hasClient: false`, `connectionHealth: no_client`).
        - `connectionHealth` is derived in this order: `no_client`, `disconnected`, `connected_not_logged`, `connected`.
        - `expiration` is the remaining lifetime in seconds computed from the stored expiration timestamp; without a stored expiration it is the negative current Unix time.
        - `avatar_url` comes from a 24-hour cache; when it is empty and the session is logged in, the avatar is fetched in the background for the next call.
        - The configuration blocks are read from the database. When that read fails, `s3_config` and `rabbitmq_config` fall back to `{"enabled": false}`.
        - The response includes secrets stored for the instance (the instance `token`, the S3 access key and the RabbitMQ URL). Call it only from trusted backends; the browser-facing `GET /connector/status` removes them.
        - `isFromAPI` mirrors `echo_api_messages`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Status snapshot of the instance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionStatusResponse"
              examples:
                connected:
                  summary: Paired and connected instance
                  value:
                    code: 200
                    data:
                      id: bec45bb93cbd24cbec32941ec3c93a12
                      name: Support Line
                      avatar_url: https://pps.whatsapp.net/v/t61.24694-24/example_avatar.jpg
                      connected: true
                      loggedIn: true
                      hasClient: true
                      connectionHealth: connected
                      lastSuccessfulConnect: 1757862000
                      autoReconnectErrors: 0
                      enableAutoReconnect: true
                      expiration: 86400
                      token: a1b2c3d4e5f6a7b8c9d0
                      jid: 5511999999999:12@s.whatsapp.net
                      webhook: https://example.com/webhook
                      events: Message,ReadReceipt
                      proxy_url: ""
                      qrcode: ""
                      skip_media_download: false
                      skip_groups: false
                      skip_newsletters: false
                      skip_broadcasts: true
                      skip_own_messages: false
                      echo_api_messages: false
                      skip_calls: false
                      call_reject_message: Sorry, I cannot take calls at the moment.
                      call_reject_type: busy
                      globalTransportSkips:
                        skipGlobalWebhook: false
                        skipGlobalRabbitMQ: false
                        skipGlobalSQS: false
                        skipGlobalRedis: false
                        skipGlobalWebSocket: false
                        skipGlobalS3: false
                      isFromAPI: false
                      auto_presence_unavailable: false
                      proxy_config:
                        enabled: false
                        proxy_url: ""
                      s3_config:
                        enabled: true
                        endpoint: https://s3.us-east-1.amazonaws.com
                        region: us-east-1
                        bucket: example-whatsapp-media
                        access_key: AKIAIOSFODNN7EXAMPLE
                        path_style: false
                        public_url: https://cdn.example.com
                        media_delivery: both
                        retention_days: 30
                        disable_acl: true
                      rabbitmq_config:
                        enabled: false
                        url: ""
                        exchange: ""
                        exchange_type: ""
                        queue: ""
                        queue_type: ""
                        routing_key: ""
                        events: ""
                        durable: false
                        auto_delete: false
                        exclusive: false
                        no_wait: false
                        delivery_mode: 0
                        dead_letter_exchange: ""
                        dead_letter_routing_key: ""
                        message_ttl: 0
                        max_length: 0
                        max_length_bytes: 0
                        queue_arguments: ""
                        exchange_arguments: ""
                      passkeyRequired: false
                      passkeyResponseSubmitted: false
                      passkeyConfirmed: false
                      passkeyConfirmationCode: ""
                      passkeySkipHandoffUX: false
                      passkeyError: ""
                      timestamp: 1757865600
                    success: true
                no_client:
                  summary: Instance created but never connected
                  value:
                    code: 200
                    data:
                      id: 5663e0c52063ec35d0ba45f7c2011c06
                      name: New Instance
                      avatar_url: ""
                      connected: false
                      loggedIn: false
                      hasClient: false
                      connectionHealth: no_client
                      lastSuccessfulConnect: 0
                      autoReconnectErrors: 0
                      enableAutoReconnect: false
                      expiration: -1757865600
                      token: f0e1d2c3b4a5968778695a4b
                      jid: ""
                      webhook: ""
                      events: ""
                      proxy_url: ""
                      qrcode: ""
                      skip_media_download: false
                      skip_groups: false
                      skip_newsletters: false
                      skip_broadcasts: false
                      skip_own_messages: false
                      echo_api_messages: false
                      skip_calls: false
                      call_reject_message: ""
                      call_reject_type: ""
                      globalTransportSkips:
                        skipGlobalWebhook: false
                        skipGlobalRabbitMQ: false
                        skipGlobalSQS: false
                        skipGlobalRedis: false
                        skipGlobalWebSocket: false
                        skipGlobalS3: false
                      isFromAPI: false
                      auto_presence_unavailable: false
                      proxy_config:
                        enabled: false
                        proxy_url: ""
                      s3_config:
                        enabled: false
                      rabbitmq_config:
                        enabled: false
                      passkeyRequired: false
                      passkeyResponseSubmitted: false
                      passkeyConfirmed: false
                      passkeyConfirmationCode: ""
                      passkeySkipHandoffUX: false
                      passkeyError: ""
                      timestamp: 1757865600
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the token lookup in the database failed, or the status could not be encoded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "sql: database is closed"
                success: false
  /session/pairphone:
    post:
      tags:
        - Session
      summary: Get a phone pairing code
      description: |
        Requests an 8-character linking code for pairing by phone number instead of scanning a QR code. The user types the code on the phone under **Linked devices > Link with phone number**.

        **Rules:**
        - The instance must be connected first (`POST /session/connect`) and not paired yet; a paired instance returns `400 already paired`, a missing client `500 no session`.
        - `Phone` is required: the full number with country code, digits only.
        - `client_type` (optional, case-insensitive) chooses the device label shown on the phone; omitted or empty keeps `chrome` ("Chrome (Linux)"). Any other value returns `400`.
        - A rejection from WhatsApp (for example an invalid number or too many attempts) is returned as `400` with the upstream error text.

        **Use Cases:**
        - Pair headless servers without a camera
        - Show a "macOS" or "Android" label instead of the default browser label
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pairphone"
            examples:
              default_chrome:
                summary: Default device label
                description: Without `client_type` the phone shows "Chrome (Linux)".
                value:
                  Phone: "5511999999999"
              macos_label:
                summary: Show the code as a macOS device
                description: The phone lists the new linked device as "macOS".
                value:
                  Phone: "5511999999999"
                  client_type: macos
              android_label:
                summary: Show the code as an Android device
                description: "`client_type` is case-insensitive, so `Android` is accepted."
                value:
                  Phone: "5511999999999"
                  client_type: Android
      responses:
        "200":
          description: Linking code generated. It must be typed on the phone before it expires.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPairPhoneResponse"
              example:
                code: 200
                data:
                  LinkingCode: 9H3J-H3J8
                success: true
        "400":
          description: "Bad Request: invalid body, missing `Phone`, unsupported `client_type`, instance already paired, or WhatsApp rejected the pairing request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone:
                  summary: Phone missing
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                invalid_client_type:
                  summary: Unsupported client_type
                  value:
                    code: 400
                    error: "invalid client_type: accepted values are unknown, chrome, edge, firefox, ie, opera, safari, electron, uwp, otherwebclient, macos, android"
                    success: false
                already_paired:
                  summary: Instance is already paired
                  value:
                    code: 400
                    error: already paired
                    success: false
                upstream_rejected:
                  summary: WhatsApp rejected the request
                  value:
                    code: 400
                    error: "server returned error 429: rate-overlimit"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory; call `POST /session/connect` first."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /session/passkey/status:
    get:
      tags:
        - Session
      summary: Get the passkey pairing state
      description: |
        Returns the in-memory passkey pairing state of the instance: the WebAuthn challenge to sign, whether a response was submitted, and the confirmation code once WhatsApp sends it.

        **Flow:**
        - `POST /session/connect`, then poll this endpoint (or call `POST /session/passkey/challenge` to obtain the challenge on demand).
        - Pass `challenge` verbatim to `navigator.credentials.get({publicKey})` at the origin `https://web.whatsapp.com`, then send the assertion to `POST /session/passkey/response`.
        - When `confirmationCode` appears and `skipHandoffUX` is false, the user compares the code on the phone and you call `POST /session/passkey/confirm`. With `skipHandoffUX: true` the gateway confirms automatically.

        **Rules:**
        - Always 200 for a valid token; without any passkey activity every field has its idle value and `challenge` is `null`.
        - `required` is `true` while a challenge is stored and not confirmed.
        - Binary WebAuthn fields (`challenge.challenge`, `allowCredentials[].id`) are base64url without padding.
        - A successful confirm clears the state, so the next call returns the idle snapshot.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Passkey state snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyStatusResponse"
              examples:
                idle:
                  summary: No passkey challenge pending
                  value:
                    code: 200
                    data:
                      required: false
                      challenge: null
                      responseSubmitted: false
                      confirmed: false
                      confirmationCode: ""
                      skipHandoffUX: false
                      error: ""
                    success: true
                challenge_pending:
                  summary: Challenge waiting for the WebAuthn assertion
                  value:
                    code: 200
                    data:
                      required: true
                      challenge:
                        challenge: ZmljdGl0aW91cy13ZWJhdXRobi1jaGFsbGVuZ2U
                        timeout: 60000
                        rpId: whatsapp.com
                        allowCredentials:
                          - id: Y3JlZGVudGlhbC1pZC0wMDAx
                            type: public-key
                            transports:
                              - internal
                              - hybrid
                        userVerification: preferred
                        extensions: null
                      responseSubmitted: false
                      confirmed: false
                      confirmationCode: ""
                      skipHandoffUX: false
                      error: ""
                    success: true
                awaiting_confirmation:
                  summary: Response submitted, confirmation code received
                  value:
                    code: 200
                    data:
                      required: true
                      challenge:
                        challenge: ZmljdGl0aW91cy13ZWJhdXRobi1jaGFsbGVuZ2U
                        timeout: 60000
                        rpId: whatsapp.com
                        allowCredentials:
                          - id: Y3JlZGVudGlhbC1pZC0wMDAx
                            type: public-key
                            transports:
                              - internal
                              - hybrid
                        userVerification: preferred
                        extensions: null
                      responseSubmitted: true
                      confirmed: false
                      confirmationCode: "482913"
                      skipHandoffUX: false
                      error: ""
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false

  /session/passkey/response:
    post:
      tags:
        - Session
      summary: Submit the WebAuthn passkey assertion
      description: |
        Forwards the WebAuthn assertion produced by the user's authenticator to WhatsApp to continue passkey pairing.

        The client must not build the `rpId` itself: it passes the `challenge` object from `GET /session/passkey/status` (which carries `rpId: whatsapp.com`) verbatim to `navigator.credentials.get()` and runs the ceremony only at the origin `https://web.whatsapp.com`.

        **Rules:**
        - Requires a client in memory (`500 no session`).
        - Rate limit: one submission per instance every 5 seconds (`429`). The limit is consumed before the body is validated, so a rejected body also starts the window.
        - Body capped at 256 KiB and decoded strictly: unknown fields or an oversized body return `400 invalid payload`.
        - Required: `id`, `rawId`, `type`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`; `response.userHandle` is optional.
        - `rawId` and every `response.*` value must be base64url without padding; `id` is forwarded as-is.
        - A rejection from WhatsApp returns `400` with the upstream error text (30-second timeout).
        - On success the state is marked `responseSubmitted`; poll the status for the confirmation code.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.PasskeyResponsePayload"
            examples:
              assertion:
                summary: WebAuthn assertion without userHandle
                description: Values copied from `PublicKeyCredential` and re-encoded as base64url without padding.
                value:
                  id: Y3JlZGVudGlhbC1pZC0wMDAx
                  rawId: Y3JlZGVudGlhbC1pZC0wMDAx
                  type: public-key
                  response:
                    clientDataJSON: eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWIud2hhdHNhcHAuY29tIn0
                    authenticatorData: ZmljdGl0aW91cy1hdXRoZW50aWNhdG9yLWRhdGE
                    signature: ZmljdGl0aW91cy1zaWduYXR1cmUtYnl0ZXM
              with_user_handle:
                summary: Assertion including userHandle
                description: Discoverable credentials return a user handle; send it base64url-encoded when present.
                value:
                  id: Y3JlZGVudGlhbC1pZC0wMDAx
                  rawId: Y3JlZGVudGlhbC1pZC0wMDAx
                  type: public-key
                  response:
                    clientDataJSON: eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWIud2hhdHNhcHAuY29tIn0
                    authenticatorData: ZmljdGl0aW91cy1hdXRoZW50aWNhdG9yLWRhdGE
                    signature: ZmljdGl0aW91cy1zaWduYXR1cmUtYnl0ZXM
                    userHandle: dXNlci1oYW5kbGUtMDAwMQ
      responses:
        "200":
          description: Assertion accepted by WhatsApp; wait for the confirmation code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeySubmitResponse"
              example:
                code: 200
                data:
                  status: submitted
                  details: Passkey response sent to WhatsApp. Await confirmation.
                success: true
        "400":
          description: "Bad Request: invalid or oversized body, missing field, value not base64url, or WhatsApp rejected the assertion."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Unknown field, invalid JSON or body over 256 KiB
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_id:
                  summary: id missing
                  value:
                    code: 400
                    error: id is required
                    success: false
                missing_signature:
                  summary: response.signature missing
                  value:
                    code: 400
                    error: response.signature is required
                    success: false
                bad_raw_id:
                  summary: rawId is not base64url
                  value:
                    code: 400
                    error: "invalid rawId: must be base64url"
                    success: false
                bad_user_handle:
                  summary: userHandle is not base64url
                  value:
                    code: 400
                    error: "invalid userHandle: must be base64url"
                    success: false
                upstream_rejected:
                  summary: WhatsApp did not accept the assertion
                  value:
                    code: 400
                    error: "info query returned status 401: not-authorized"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "429":
          description: "Too Many Requests: a submission was already made for this instance in the last 5 seconds."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 429
                error: "rate limit exceeded: try again in 5 seconds"
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory; call `POST /session/connect` first."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /session/passkey/confirm:
    post:
      tags:
        - Session
      summary: Confirm passkey pairing
      description: |
        Tells WhatsApp that the user verified the passkey confirmation code on the phone, completing passkey pairing.

        **Rules:**
        - No request body is read.
        - Needed only when `GET /session/passkey/status` reports `skipHandoffUX: false`; with `skipHandoffUX: true` the gateway sends the confirmation automatically when the code arrives.
        - Requires a client in memory (`500 no session`).
        - A rejection from WhatsApp, or calling it before a response was submitted, returns `400` with the upstream error text (30-second timeout).
        - On success the passkey state is cleared; follow the `PairSuccess` event or `GET /session/status` until `loggedIn` is true.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Confirmation sent; pairing completes shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyConfirmResponse"
              example:
                code: 200
                data:
                  status: confirmed
                  details: Passkey confirmation sent. Pairing should complete shortly.
                success: true
        "400":
          description: "Bad Request: WhatsApp rejected the confirmation, or no passkey response was submitted before."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_response_submitted:
                  summary: Confirm called before submitting the assertion
                  value:
                    code: 400
                    error: no passkey linking cache available
                    success: false
                upstream_rejected:
                  summary: WhatsApp did not accept the confirmation
                  value:
                    code: 400
                    error: info query timed out
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /session/passkey/challenge:
    post:
      tags:
        - Session
      summary: Fetch the passkey challenge on demand
      description: |
        Asks WhatsApp for the WebAuthn request options right away and stores them, so `GET /session/passkey/status` returns the challenge immediately instead of waiting for the `PasskeyRequest` event.

        **Rules:**
        - No request body is read. The instance must be connected (`POST /session/connect` first).
        - Without a client the call returns `500 no session`.
        - A disconnected client returns **503** `not_connected`; an account outside the passkey cohort (normal QR or phone-code accounts), or a refused request, returns **503** `passkey_unavailable` with the upstream reason. Fall back to `GET /session/qr` or `POST /session/pairphone` in that case.
        - Both 503 bodies keep the legacy envelope: `success: true` at the top level and `error`/`reason` inside `data`.
        - 30-second timeout for the WhatsApp request.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Challenge fetched and stored for this instance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyChallengeResponse"
              example:
                code: 200
                data:
                  required: true
                  challenge:
                    challenge: ZmljdGl0aW91cy13ZWJhdXRobi1jaGFsbGVuZ2U
                    timeout: 60000
                    rpId: whatsapp.com
                    allowCredentials:
                      - id: Y3JlZGVudGlhbC1pZC0wMDAx
                        type: public-key
                        transports:
                          - internal
                          - hybrid
                    userVerification: preferred
                    extensions: null
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false
        "503":
          description: "Service Unavailable (legacy envelope, `success: true`): the client is not connected, or passkey pairing is not available for this account."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyUnavailableResponse"
              examples:
                not_connected:
                  summary: Client is not connected
                  value:
                    code: 503
                    data:
                      error: not_connected
                      reason: connect the instance before probing the passkey challenge
                    success: true
                passkey_unavailable:
                  summary: Account outside the passkey cohort
                  value:
                    code: 503
                    data:
                      error: passkey_unavailable
                      reason: "info query returned status 404: item-not-found"
                    success: true

  /session/connector/token:
    post:
      tags:
        - Connector
      summary: Mint an ephemeral Connector token
      description: |
        Issues a short-lived token bound to this instance for the embeddable Connector widget, so the browser never receives the real instance token.

        **Rules:**
        - Call it from your backend with the real instance token; hand only `data.token` to the browser (for example `ZZ.connector.open({ token })`).
        - The token starts with `ct_`, lives for `CONNECTOR_TOKEN_TTL` seconds (default 600) and is accepted only on `/connector/*` routes, in the `token` header or the `token` query parameter.
        - Tokens are kept in memory: a gateway restart invalidates them.
        - Rate limit per real token: `CONNECTOR_TOKEN_RATE_PER_MIN` mints per minute (default 60); above it the call returns `429`.
        - No request body is read.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Ephemeral token issued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectorTokenResponse"
              example:
                code: 200
                data:
                  token: ct_Qm9ndXNFeGFtcGxlVG9rZW5WYWx1ZTAwMDAwMDAwMDAwMA
                  expiresIn: 600
                  tokenType: connector
                success: true
        "401":
          description: Missing or invalid real instance token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "429":
          description: "Too Many Requests: the per-token mint limit for the current minute was reached."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 429
                error: "mint rate limit exceeded: try again shortly"
                success: false
        "500":
          description: "Internal Server Error: random bytes for the token could not be generated."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "read /dev/urandom: resource temporarily unavailable"
                success: false

  /connector/connect:
    post:
      tags:
        - Connector
      summary: "[Connector] Connect the instance"
      description: |
        Browser-facing version of `POST /session/connect`: the embeddable widget starts the WhatsApp client with an ephemeral Connector token instead of the real instance token.

        **Rules:**
        - Authenticate with the `ct_...` token from `POST /session/connector/token` (header `token` or `?token=`); an unknown or expired token returns `401`.
        - Same body, side effects and errors as `POST /session/connect`: the body is required (send at least `{}`), `Subscribe` replaces the stored subscription and an omitted list clears it, and `Immediate: false` blocks for about 10 seconds.
        - Nothing is redacted: `data.webhook` and `data.jid` are returned to the browser. Prefer `Immediate: true` and poll `GET /connector/status`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Connect"
            examples:
              widget_immediate:
                summary: Start pairing and return immediately
                description: The widget keeps the stored subscription it sends and polls the status afterwards.
                value:
                  Subscribe:
                    - Message
                  Immediate: true
              widget_wait:
                summary: Wait up to 10 seconds for the socket
                description: Answers 500 `failed to connect` when the socket is not connected after the wait.
                value:
                  Subscribe:
                    - All
                  Immediate: false
      responses:
        "200":
          description: Client started for the instance bound to the Connector token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionConnectResponse"
              example:
                code: 200
                data:
                  details: Connected!
                  events: Message
                  jid: ""
                  skip_media_download: false
                  webhook: https://example.com/webhook
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the client is already connected, or the socket did not connect within the wait."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                already_connected:
                  summary: A connected client already exists
                  value:
                    code: 500
                    error: already connected
                    success: false
                failed_to_connect:
                  summary: Socket not connected after the wait
                  value:
                    code: 500
                    error: failed to connect
                    success: false

  /connector/disconnect:
    post:
      tags:
        - Connector
      summary: "[Connector] Disconnect the instance"
      description: |
        Lets the Connector widget close the WhatsApp socket (for example when the user cancels pairing) without holding the real instance token.

        **Rules:**
        - Accepts only a valid, unexpired ephemeral token minted by `POST /session/connector/token`; otherwise `401`.
        - Runs the `POST /session/disconnect` handler: no body, `500 no session` without a client, a synthetic `Disconnected` webhook event when the client was connected or logged in.
        - The paired session is kept; the stored QR code is cleared.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Client disconnected; the paired session is kept.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionDisconnectResponse"
              example:
                code: 200
                data:
                  Details: Disconnected successfully
                  wasConnected: true
                  wasLoggedIn: false
                success: true
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for the instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /connector/status:
    get:
      tags:
        - Connector
      summary: "[Connector] Get the redacted connection status"
      description: |
        Returns the connection and pairing snapshot the widget needs to drive its UI, with every credential of the instance removed.

        **Rules:**
        - Requires the ephemeral `ct_...` token (header `token` or query `token`); the real instance token is rejected here with `401`.
        - Same snapshot as `GET /session/status` except that `token`, `webhook`, `events`, `proxy_url`, `proxy_config`, `s3_config` and `rabbitmq_config` are removed.
        - Use `connected`, `loggedIn`, `connectionHealth`, `qrcode` and the `passkey*` fields to decide which pairing step to show.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Redacted status snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectorStatusResponse"
              example:
                code: 200
                data:
                  id: bec45bb93cbd24cbec32941ec3c93a12
                  name: Support Line
                  avatar_url: ""
                  connected: true
                  loggedIn: false
                  hasClient: true
                  connectionHealth: connected_not_logged
                  lastSuccessfulConnect: 1757862000
                  autoReconnectErrors: 0
                  enableAutoReconnect: true
                  expiration: 86400
                  jid: ""
                  qrcode: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAA
                  skip_media_download: false
                  skip_groups: false
                  skip_newsletters: false
                  skip_broadcasts: false
                  skip_own_messages: false
                  echo_api_messages: false
                  skip_calls: false
                  call_reject_message: ""
                  call_reject_type: ""
                  globalTransportSkips:
                    skipGlobalWebhook: false
                    skipGlobalRabbitMQ: false
                    skipGlobalSQS: false
                    skipGlobalRedis: false
                    skipGlobalWebSocket: false
                    skipGlobalS3: false
                  isFromAPI: false
                  auto_presence_unavailable: false
                  passkeyRequired: false
                  passkeyResponseSubmitted: false
                  passkeyConfirmed: false
                  passkeyConfirmationCode: ""
                  passkeySkipHandoffUX: false
                  passkeyError: ""
                  timestamp: 1757865600
                success: true
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the token lookup in the database failed, or the status could not be encoded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "sql: database is closed"
                success: false

  /connector/qr:
    get:
      tags:
        - Connector
      summary: "[Connector] Get the pairing QR code"
      description: |
        Returns the current pairing QR code so the widget can render it in the browser while the user scans it on the phone.

        **Rules:**
        - Authenticated by the short-lived Connector token only (header or query `token`); `401` once it expires.
        - Runs the `GET /session/qr` handler: the client must be connected and not logged in (`500 no session`, `500 not connected`, `500 already logged in`).
        - The code rotates every few seconds; poll it or follow `GET /connector/status` (`qrcode`).
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Current QR code (empty strings until the first code is generated).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionQRResponse"
              examples:
                qr_available:
                  summary: QR code ready to scan
                  value:
                    code: 200
                    data:
                      QRCode: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAA
                      code: 2@Xq3example0000QrTextValue,Zq9exampleNoiseKey=,Kp2exampleIdentityKey=,Tt5exampleAdvSecret=,1
                    success: true
                not_generated_yet:
                  summary: Connected, first code not generated yet
                  value:
                    code: 200
                    data:
                      QRCode: ""
                      code: ""
                    success: true
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no client, client not connected, instance already logged in, or the stored QR code could not be read."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No client in memory
                  value:
                    code: 500
                    error: no session
                    success: false
                not_connected:
                  summary: Client exists but is not connected
                  value:
                    code: 500
                    error: not connected
                    success: false
                already_logged_in:
                  summary: Instance is already paired
                  value:
                    code: 500
                    error: already logged in
                    success: false

  /connector/pairphone:
    post:
      tags:
        - Connector
      summary: "[Connector] Get a phone pairing code"
      description: |
        Requests a phone linking code from the widget, for users who pair by typing a code instead of scanning the QR.

        **Rules:**
        - Send the ephemeral token (not the real instance token) in the `token` header or query; expired tokens get `401`.
        - Same body, rules and errors as `POST /session/pairphone`: `Phone` required, optional case-insensitive `client_type` (default `chrome`), `400 already paired` for a paired instance and `500 no session` without a client.
        - WhatsApp rejections come back as `400` with the upstream text.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pairphone"
            examples:
              default_chrome:
                summary: Default device label
                description: Without `client_type` the phone shows "Chrome (Linux)".
                value:
                  Phone: "5511999999999"
              macos_label:
                summary: Show the code as a macOS device
                description: The phone lists the new linked device as "macOS".
                value:
                  Phone: "5511999999999"
                  client_type: macos
      responses:
        "200":
          description: Linking code generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPairPhoneResponse"
              example:
                code: 200
                data:
                  LinkingCode: 9H3J-H3J8
                success: true
        "400":
          description: "Bad Request: invalid body, missing `Phone`, unsupported `client_type`, instance already paired, or WhatsApp rejected the request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone:
                  summary: Phone missing
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                invalid_client_type:
                  summary: Unsupported client_type
                  value:
                    code: 400
                    error: "invalid client_type: accepted values are unknown, chrome, edge, firefox, ie, opera, safari, electron, uwp, otherwebclient, macos, android"
                    success: false
                already_paired:
                  summary: Instance is already paired
                  value:
                    code: 400
                    error: already paired
                    success: false
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory; connect first."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /connector/passkey/status:
    get:
      tags:
        - Connector
      summary: "[Connector] Get the passkey pairing state"
      description: |
        Returns the passkey pairing state to the widget, which runs the WebAuthn ceremony in the browser with the challenge it contains.

        **Rules:**
        - Auth: a `ct_...` Connector token that has not expired, in the `token` header or query; else `401`.
        - Same snapshot as `GET /session/passkey/status`; it holds no credentials, so nothing is redacted.
        - Pass `challenge` verbatim to `navigator.credentials.get({publicKey})` at the origin `https://web.whatsapp.com`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Passkey state snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyStatusResponse"
              examples:
                idle:
                  summary: No passkey challenge pending
                  value:
                    code: 200
                    data:
                      required: false
                      challenge: null
                      responseSubmitted: false
                      confirmed: false
                      confirmationCode: ""
                      skipHandoffUX: false
                      error: ""
                    success: true
                challenge_pending:
                  summary: Challenge waiting for the WebAuthn assertion
                  value:
                    code: 200
                    data:
                      required: true
                      challenge:
                        challenge: ZmljdGl0aW91cy13ZWJhdXRobi1jaGFsbGVuZ2U
                        timeout: 60000
                        rpId: whatsapp.com
                        allowCredentials:
                          - id: Y3JlZGVudGlhbC1pZC0wMDAx
                            type: public-key
                            transports:
                              - internal
                              - hybrid
                        userVerification: preferred
                        extensions: null
                      responseSubmitted: false
                      confirmed: false
                      confirmationCode: ""
                      skipHandoffUX: false
                      error: ""
                    success: true
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false

  /connector/passkey/challenge:
    post:
      tags:
        - Connector
      summary: "[Connector] Fetch the passkey challenge on demand"
      description: |
        Lets the widget force WhatsApp to issue the WebAuthn challenge immediately, so the passkey tab does not wait for the reactive `PasskeyRequest` event.

        **Rules:**
        - The ephemeral token minted for this instance is the only accepted credential (header or query `token`); `401` otherwise.
        - Runs the `POST /session/passkey/challenge` handler: no body, the instance must be connected.
        - **503** bodies (`not_connected`, `passkey_unavailable`) keep the legacy envelope with `success: true`; on `passkey_unavailable` the widget should switch to the QR or phone-code tab.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Challenge fetched and stored.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyChallengeResponse"
              example:
                code: 200
                data:
                  required: true
                  challenge:
                    challenge: ZmljdGl0aW91cy13ZWJhdXRobi1jaGFsbGVuZ2U
                    timeout: 60000
                    rpId: whatsapp.com
                    allowCredentials:
                      - id: Y3JlZGVudGlhbC1pZC0wMDAx
                        type: public-key
                        transports:
                          - internal
                          - hybrid
                    userVerification: preferred
                    extensions: null
                success: true
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for the instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false
        "503":
          description: "Service Unavailable (legacy envelope, `success: true`): client not connected, or passkey pairing not available for the account."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyUnavailableResponse"
              examples:
                not_connected:
                  summary: Client is not connected
                  value:
                    code: 503
                    data:
                      error: not_connected
                      reason: connect the instance before probing the passkey challenge
                    success: true
                passkey_unavailable:
                  summary: Account outside the passkey cohort
                  value:
                    code: 503
                    data:
                      error: passkey_unavailable
                      reason: "info query returned status 404: item-not-found"
                    success: true

  /connector/passkey/response:
    post:
      tags:
        - Connector
      summary: "[Connector] Submit the WebAuthn passkey assertion"
      description: |
        Receives the assertion that the browser obtained from `navigator.credentials.get()` and forwards it to WhatsApp on behalf of the widget.

        **Rules:**
        - Use the Connector token issued by your backend (header or query `token`); a missing or expired one is answered with `401`.
        - Same strict body and errors as `POST /session/passkey/response`: 256 KiB cap, unknown fields rejected, base64url without padding for `rawId` and `response.*`, one submission every 5 seconds (`429`).
        - After `200`, poll `GET /connector/passkey/status` for the confirmation code.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.PasskeyResponsePayload"
            examples:
              assertion:
                summary: WebAuthn assertion without userHandle
                description: Binary values from `PublicKeyCredential` re-encoded as base64url without padding.
                value:
                  id: Y3JlZGVudGlhbC1pZC0wMDAx
                  rawId: Y3JlZGVudGlhbC1pZC0wMDAx
                  type: public-key
                  response:
                    clientDataJSON: eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWIud2hhdHNhcHAuY29tIn0
                    authenticatorData: ZmljdGl0aW91cy1hdXRoZW50aWNhdG9yLWRhdGE
                    signature: ZmljdGl0aW91cy1zaWduYXR1cmUtYnl0ZXM
              with_user_handle:
                summary: Assertion including userHandle
                description: Send `userHandle` when the authenticator returns one.
                value:
                  id: Y3JlZGVudGlhbC1pZC0wMDAx
                  rawId: Y3JlZGVudGlhbC1pZC0wMDAx
                  type: public-key
                  response:
                    clientDataJSON: eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWIud2hhdHNhcHAuY29tIn0
                    authenticatorData: ZmljdGl0aW91cy1hdXRoZW50aWNhdG9yLWRhdGE
                    signature: ZmljdGl0aW91cy1zaWduYXR1cmUtYnl0ZXM
                    userHandle: dXNlci1oYW5kbGUtMDAwMQ
      responses:
        "200":
          description: Assertion accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeySubmitResponse"
              example:
                code: 200
                data:
                  status: submitted
                  details: Passkey response sent to WhatsApp. Await confirmation.
                success: true
        "400":
          description: "Bad Request: invalid or oversized body, missing field, value not base64url, or WhatsApp rejected the assertion."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Unknown field, invalid JSON or body over 256 KiB
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_type:
                  summary: type missing
                  value:
                    code: 400
                    error: type is required
                    success: false
                bad_signature:
                  summary: signature is not base64url
                  value:
                    code: 400
                    error: "invalid signature: must be base64url"
                    success: false
                upstream_rejected:
                  summary: WhatsApp did not accept the assertion
                  value:
                    code: 400
                    error: "info query returned status 401: not-authorized"
                    success: false
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "429":
          description: "Too Many Requests: a submission was already made for this instance in the last 5 seconds."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 429
                error: "rate limit exceeded: try again in 5 seconds"
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for the instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /connector/passkey/confirm:
    post:
      tags:
        - Connector
      summary: "[Connector] Confirm passkey pairing"
      description: |
        Sends the final passkey confirmation from the widget after the user compared the confirmation code on the phone.

        **Rules:**
        - Authorized by the ephemeral Connector token (header or query `token`), never by the real instance token; `401` when it is not valid.
        - Needed only when the status reports `skipHandoffUX: false`; otherwise the gateway already confirmed.
        - Runs the `POST /session/passkey/confirm` handler: no body, `400` with the upstream text when WhatsApp refuses, `500 no session` without a client.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Confirmation sent; pairing completes shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPasskeyConfirmResponse"
              example:
                code: 200
                data:
                  status: confirmed
                  details: Passkey confirmation sent. Pairing should complete shortly.
                success: true
        "400":
          description: "Bad Request: WhatsApp rejected the confirmation, or no passkey response was submitted before."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_response_submitted:
                  summary: Confirm called before submitting the assertion
                  value:
                    code: 400
                    error: no passkey linking cache available
                    success: false
                key_not_ready:
                  summary: Confirmation code not received yet
                  value:
                    code: 400
                    error: passkey linking cache does not have an encryption key yet
                    success: false
        "401":
          description: Connector token missing, unknown or expired (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: there is no client in memory for the instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false

  /session/import:
    post:
      tags:
        - Session
      summary: Import an existing web.whatsapp.com session (BETA)
      description: |
        Converts a web.whatsapp.com device identity into a native device of this instance and boots it, as an alternative to QR, phone-code or passkey pairing. The payload is impersonation-grade key material: guard this endpoint and never log the body.

        **Rules:**
        - Body up to 16 MiB. A body with `meJid` is parsed as a structured `WebDeviceSnapshot`; a body with `source: wa-web` or an `indexeddb` object is treated as a generic companion dump; anything else returns `400`.
        - Structured snapshot, required: `meJid`, non-zero `registrationId`, `noiseKey.priv`, `identityKey.priv`, `signedPreKey.priv` (32 bytes), `signedPreKey.signature` (64 bytes) and the four `account` fields. Key material is base64 (standard or URL alphabet, padded or not); `pub` values are ignored because they are derived from `priv`.
        - Optional: `lid`, `advSecretKey` (a random key is kept when omitted), `platform`, `pushName`, `businessName`.
        - Invalid key material returns `422 snapshot conversion failed: ...`.
        - **Generic dump:** automatic extraction is not implemented yet, so a dump is always answered with `422` listing the missing fields (legacy envelope, `success: true`). Nothing is fabricated.
        - On success the device is stored, `users.jid` is updated and the client starts in the background with the `Message` subscription only. Poll `GET /session/status` until `loggedIn` is true and call `POST /session/connect` to set the subscription you need.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.WebDeviceSnapshot"
            examples:
              structured_snapshot:
                summary: Structured snapshot with every field
                description: All key material in base64. The values are synthetic and do not open a real session.
                value:
                  meJid: 5511999999999:12@s.whatsapp.net
                  lid: 123456789012345:12@lid
                  registrationId: 12345
                  noiseKey:
                    priv: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=
                  identityKey:
                    priv: ISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0A=
                  signedPreKey:
                    keyId: 1
                    priv: QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2A=
                    signature: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foA==
                  account:
                    details: CwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6
                    accountSignature: FRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVA==
                    accountSignatureKey: HyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4=
                    deviceSignature: KSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaA==
                  advSecretKey: MzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVI=
                  platform: chrome
                  pushName: Support Line
                  businessName: ""
              minimal_snapshot:
                summary: Only the required fields
                description: Without `advSecretKey` a fresh random ADV secret is kept; `lid`, `platform` and names stay empty.
                value:
                  meJid: 5511999999999:12@s.whatsapp.net
                  registrationId: 12345
                  noiseKey:
                    priv: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=
                  identityKey:
                    priv: ISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0A=
                  signedPreKey:
                    keyId: 1
                    priv: QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2A=
                    signature: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foA==
                  account:
                    details: CwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6
                    accountSignature: FRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVA==
                    accountSignatureKey: HyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4=
                    deviceSignature: KSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaA==
              wa_web_dump:
                summary: Generic companion dump (always answered with 422)
                description: Recognized by `source` or `indexeddb`; the extractor reports the identity fields it cannot resolve.
                value:
                  source: wa-web
                  indexeddb:
                    wawc: {}
      responses:
        "200":
          description: Device stored; the client is booting in the background.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionImportResponse"
              example:
                code: 200
                data:
                  status: imported
                  jid: 5511999999999:12@s.whatsapp.net
                  note: device booting (BETA); poll /session/status for loggedIn
                success: true
        "400":
          description: "Bad Request: the body could not be read (or exceeds 16 MiB), is not JSON, or matches neither accepted shape."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_body:
                  summary: Body unreadable or over 16 MiB
                  value:
                    code: 400
                    error: could not read body
                    success: false
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid JSON
                    success: false
                invalid_snapshot:
                  summary: meJid present but a field has the wrong type
                  value:
                    code: 400
                    error: invalid snapshot
                    success: false
                unrecognized_payload:
                  summary: Neither a snapshot nor a wa-web dump
                  value:
                    code: 400
                    error: "unrecognized import payload: expected a WebDeviceSnapshot or a wa-web dump"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "422":
          description: "Unprocessable Entity: the snapshot key material is invalid (error envelope), or a generic dump could not be converted (legacy envelope with `success: true` and details in `data`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionImportUnprocessableResponse"
              examples:
                conversion_failed:
                  summary: Invalid key material in the snapshot
                  value:
                    code: 422
                    error: "snapshot conversion failed: noiseKey: priv: expected 32 bytes, got 16"
                    success: false
                missing_registration_id:
                  summary: registrationId missing or zero
                  value:
                    code: 422
                    error: "snapshot conversion failed: registrationId is required and must be non-zero"
                    success: false
                incomplete_dump:
                  summary: Generic dump without resolvable identity fields
                  value:
                    code: 422
                    data:
                      error: "incomplete wa-web dump: auto-extraction could not resolve required fields"
                      missing:
                        - meJid
                        - registrationId
                        - noiseKey
                        - identityKey
                        - signedPreKey
                        - account
                      hint: supply a structured WebDeviceSnapshot (meJid, registrationId, noiseKey, identityKey, signedPreKey, account) — see docs/session-import.md
                    success: true
        "500":
          description: "Internal Server Error: the device store is not initialized, or the device could not be persisted."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                container_not_initialized:
                  summary: Device store not initialized
                  value:
                    code: 500
                    error: whatsmeow container not initialized
                    success: false
                persist_failed:
                  summary: Device could not be stored
                  value:
                    code: 500
                    error: "persist device: sql: database is closed"
                    success: false

  /session/qr:
    get:
      tags:
        - Session
      summary: Get the pairing QR code
      description: |
        Returns the current pairing QR code of a connected instance that is not logged in yet, as a PNG data URL and as the raw code text.

        **Rules:**
        - Call `POST /session/connect` first. Without a client the call returns `500 no session`; with a disconnected client `500 not connected`.
        - A logged-in instance returns `500 already logged in`.
        - WhatsApp rotates the code every few seconds; poll this endpoint or subscribe to the `QR` event instead of caching it.
        - Right after connecting, before the first code is generated, both fields can be empty strings.
        - `code` is the raw text encoded in the QR image, useful for rendering the QR yourself.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Current QR code (empty strings until the first code is generated).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionQRResponse"
              examples:
                qr_available:
                  summary: QR code ready to scan
                  value:
                    code: 200
                    data:
                      QRCode: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAA
                      code: 2@Xq3example0000QrTextValue,Zq9exampleNoiseKey=,Kp2exampleIdentityKey=,Tt5exampleAdvSecret=,1
                    success: true
                not_generated_yet:
                  summary: Connected, first code not generated yet
                  value:
                    code: 200
                    data:
                      QRCode: ""
                      code: ""
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no client, client not connected, instance already logged in, or the stored QR code could not be read."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No client in memory
                  value:
                    code: 500
                    error: no session
                    success: false
                not_connected:
                  summary: Client exists but is not connected
                  value:
                    code: 500
                    error: not connected
                    success: false
                already_logged_in:
                  summary: Instance is already paired
                  value:
                    code: 500
                    error: already logged in
                    success: false
                database_error:
                  summary: Stored QR code could not be read
                  value:
                    code: 500
                    error: "sql: database is closed"
                    success: false
  /session/network-route:
    post:
      tags:
        - Session Network Route
      summary: Provision or update the mobile network route
      operationId: applySessionNetworkRoute
      description: |
        Provisions an isolated `tsnet` node for this session that egresses through a stable Tailscale exit node (typically a phone on mobile data), then reconnects the WhatsApp client through it.

        **Features:**
        - The current client is disconnected before the route is prepared; the node starts, selects the exit node, opens an authenticated SOCKS5 listener on `127.0.0.1` and runs a SOCKS/TLS preflight.
        - When the client was connected before and the route becomes ready, a reconnect is scheduled (`reconnectScheduled: true`).
        - `authKey` is write-only: used once during node start, never persisted, returned or logged.

        **Rules:**
        - Strict body: unknown fields or invalid JSON return `400 network_route_payload_invalid`.
        - `mode` must be `mobile` and `tcpOnly` must be `true`; native Calls/UDP stay disabled while the route exists.
        - `failClosed` is a plain boolean and **omitting it means `false`**: while the route is not ready the session may leave through the default egress (legacy proxy or direct). Send `true` to hold traffic instead.
        - `exitNodeId`: 3-128 characters `[A-Za-z0-9_-]`. `idempotencyKey`: 8-160 characters `[A-Za-z0-9._:-]`. `authKey`: 12-512 printable characters starting with `tskey-auth-`.
        - `routeVersion` is a fencing token (>= 1). A lower version returns `409 network_route_stale_version`; the same version with a different configuration or key returns `409 network_route_version_conflict`; an exact retry returns the current state (200 when ready, 503 with the stored error code when not).
        - A failed start leaves the route `blocked` (`503 network_route_prepare_failed`, redacted cause in `details`). Retry with a higher `routeVersion` and a new one-use auth key, or remove the route.
        - Error bodies raised by the route manager also carry `data` with the route state; for validation errors that state is empty.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.NetworkRouteRequest"
            examples:
              first_apply_fail_closed:
                summary: First route, hold traffic while not ready
                description: Version 1 with `failClosed` true, so nothing leaves through another IP while the exit node is unavailable.
                value:
                  mode: mobile
                  authKey: tskey-auth-kExample0000CNTRL-ExampleSecretValue0000
                  exitNodeId: nStableMobileExit01
                  routeVersion: 1
                  idempotencyKey: route-2026-09-15-0001
                  failClosed: true
                  tcpOnly: true
              new_version_fail_open:
                summary: New version that falls back to the default egress
                description: A higher `routeVersion` with a new auth key; omitting `failClosed` would have the same fail-open effect as `false`.
                value:
                  mode: mobile
                  authKey: tskey-auth-kExample0001CNTRL-ExampleSecretValue0001
                  exitNodeId: nStableMobileExit02
                  routeVersion: 2
                  idempotencyKey: route-2026-09-15-0002
                  failClosed: false
                  tcpOnly: true
      responses:
        "200":
          description: Route ready, or an exact retry of the current version that is already ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteApplyResponse"
              examples:
                ready_reconnect:
                  summary: Route ready, client reconnect scheduled
                  value:
                    code: 200
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 1
                      appliedVersion: 1
                      failClosed: true
                      tcpOnly: true
                      status: ready
                      ready: true
                    details: network route ready
                    reconnectScheduled: true
                    success: true
                idempotent_retry:
                  summary: Exact retry of the ready version
                  value:
                    code: 200
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 1
                      appliedVersion: 1
                      failClosed: true
                      tcpOnly: true
                      status: ready
                      ready: true
                    details: network route ready
                    reconnectScheduled: false
                    success: true
        "400":
          description: "Bad Request: invalid JSON or unknown field, or a field failed validation. Validation errors carry an empty `data` route state."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              examples:
                payload_invalid:
                  summary: Invalid JSON or unknown field
                  value:
                    code: 400
                    error: network_route_payload_invalid
                    success: false
                tcp_only_required:
                  summary: tcpOnly is not true
                  value:
                    code: 400
                    data:
                      mode: ""
                      exitNodeId: ""
                      desiredVersion: 0
                      appliedVersion: 0
                      failClosed: false
                      tcpOnly: false
                      status: ""
                      ready: false
                    error: network_route_mobile_requires_tcp_only
                    success: false
                auth_key_invalid:
                  summary: authKey missing or malformed
                  value:
                    code: 400
                    data:
                      mode: ""
                      exitNodeId: ""
                      desiredVersion: 0
                      appliedVersion: 0
                      failClosed: false
                      tcpOnly: false
                      status: ""
                      ready: false
                    error: network_route_auth_key_invalid
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: fencing or idempotency violation; the stored route is not changed. `data` holds the stored route."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              examples:
                stale_version:
                  summary: routeVersion lower than the stored version
                  value:
                    code: 409
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 3
                      appliedVersion: 3
                      failClosed: true
                      tcpOnly: true
                      status: ready
                      ready: true
                    error: network_route_stale_version
                    success: false
                version_conflict:
                  summary: Same version, different configuration or idempotency key
                  value:
                    code: 409
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 3
                      appliedVersion: 3
                      failClosed: true
                      tcpOnly: true
                      status: ready
                      ready: true
                    error: network_route_version_conflict
                    success: false
        "503":
          description: "Service Unavailable: route manager or store unavailable, the client could not be stopped, the node failed to start (route left `blocked`), or an exact retry of a version that is not ready."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              examples:
                prepare_failed:
                  summary: Node start or preflight failed; route blocked
                  value:
                    code: 503
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 2
                      appliedVersion: 1
                      failClosed: true
                      tcpOnly: true
                      status: blocked
                      ready: false
                      errorCode: network_route_prepare_failed
                    details: "exit node nStableMobileExit01 is offline"
                    error: network_route_prepare_failed
                    success: false
                manager_unavailable:
                  summary: Route manager not initialized in this process
                  value:
                    code: 503
                    error: network_route_manager_unavailable
                    success: false
                not_ready_retry:
                  summary: Exact retry of a blocked version
                  value:
                    code: 503
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 2
                      appliedVersion: 1
                      failClosed: true
                      tcpOnly: true
                      status: blocked
                      ready: false
                      errorCode: network_route_prepare_failed
                    error: network_route_prepare_failed
                    success: false
    get:
      tags:
        - Session Network Route
      summary: Get the mobile network route state
      operationId: getSessionNetworkRoute
      description: |
        Returns the non-secret state of this session's mobile route: desired and applied versions, fail mode, status and live readiness. With `probe=egress` it also proves the public IP the route really exits from.

        **Rules:**
        - Never returns the auth key, SOCKS credentials, proxy URL or state paths.
        - `ready: true` requires `status: ready`, `desiredVersion == appliedVersion` and a live node for that version in this process; after a gateway restart it stays false until the node is restored.
        - `probe=egress` makes a real external request through the route (10-second timeout) and adds `egressIP` at the top level. When the probe fails the call still answers 200 with `egressProbeError` instead. Any other `probe` value is ignored.
        - `404 network_route_not_configured` when the session has no route.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: probe
          required: false
          description: Send `egress` to measure the route's real public exit IP (external request). Other values only return the stored state.
          schema:
            type: string
            enum:
              - egress
          example: egress
      responses:
        "200":
          description: Route state, plus `egressIP` or `egressProbeError` when `probe=egress`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteGetResponse"
              examples:
                state:
                  summary: Route state without probe
                  value:
                    code: 200
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 3
                      appliedVersion: 3
                      failClosed: true
                      tcpOnly: true
                      status: ready
                      ready: true
                    success: true
                egress_probe:
                  summary: probe=egress succeeded
                  value:
                    code: 200
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 3
                      appliedVersion: 3
                      failClosed: true
                      tcpOnly: true
                      status: ready
                      ready: true
                    egressIP: 203.0.113.45
                    success: true
                egress_probe_failed:
                  summary: probe=egress failed (route not active)
                  value:
                    code: 200
                    data:
                      mode: mobile
                      exitNodeId: nStableMobileExit01
                      desiredVersion: 2
                      appliedVersion: 1
                      failClosed: true
                      tcpOnly: true
                      status: blocked
                      ready: false
                      errorCode: network_route_prepare_failed
                    egressProbeError: network_route_egress_probe_failed
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no mobile route is configured for this session."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              example:
                code: 404
                error: network_route_not_configured
                success: false
        "503":
          description: "Service Unavailable: the route manager is not initialized, or the route metadata could not be read."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              examples:
                manager_unavailable:
                  summary: Route manager not initialized
                  value:
                    code: 503
                    error: network_route_manager_unavailable
                    success: false
                store_unavailable:
                  summary: Route metadata unreadable
                  value:
                    code: 503
                    error: network_route_store_unavailable
                    success: false
    delete:
      tags:
        - Session Network Route
      summary: Remove the mobile network route
      operationId: removeSessionNetworkRoute
      description: |
        Removes this session's mobile route on explicit operator request: the live node is logged out and closed, the stored row is deleted and the state directory is wiped. Afterwards the session egresses through its legacy `proxy_url`, or directly when none is set, so the peer sees a different IP.

        **Rules:**
        - No body. The client is disconnected first and any stored QR code is cleared.
        - `data` is the route as read right before removal.
        - A reconnect is scheduled (`reconnectScheduled: true`) when the client was connected, or when a fail-closed route that was not ready had been holding the session offline, so a fresh QR code is produced. A session stopped on purpose over a healthy route stays stopped.
        - Idempotent by absence: a second call returns `404 network_route_not_configured`.
        - `503 network_route_revoke_failed` may leave a partial removal; repeat the call, and a `404` then confirms the route is gone.
        - The next apply accepts any valid `routeVersion` and needs a new one-use auth key.

        **Use Cases:**
        - Unblock a session held offline by a route that will not converge (`status: blocked`)
        - Recover after the route runtime did not come back from a gateway restart
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Route removed; `data` is its last state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteApplyResponse"
              example:
                code: 200
                data:
                  mode: mobile
                  exitNodeId: nStableMobileExit01
                  desiredVersion: 4
                  appliedVersion: 3
                  failClosed: true
                  tcpOnly: true
                  status: blocked
                  ready: false
                  errorCode: network_route_prepare_failed
                details: "network route removed; this session egresses through its legacy proxy_url on the next connect, or directly when none is set"
                reconnectScheduled: true
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no route is configured (also the answer to a repeated removal)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              example:
                code: 404
                error: network_route_not_configured
                success: false
        "503":
          description: "Service Unavailable: route manager not initialized, metadata unreadable, or the removal failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkRouteErrorResponse"
              examples:
                manager_unavailable:
                  summary: Route manager not initialized
                  value:
                    code: 503
                    error: network_route_manager_unavailable
                    success: false
                store_unavailable:
                  summary: Route metadata unreadable
                  value:
                    code: 503
                    error: network_route_store_unavailable
                    success: false
                revoke_failed:
                  summary: Node close, row delete or cleanup failed
                  value:
                    code: 503
                    error: network_route_revoke_failed
                    success: false

  /session/proxy:
    post:
      tags:
        - Session
      summary: Set or remove the connection proxy
      description: |
        Stores or removes the legacy proxy used by the WhatsApp connection of this instance (`users.proxy_url`). The proxy is applied on the next connect.

        **Rules:**
        - The instance must be **disconnected**: a connected client returns `400 cannot set proxy while connected. Please disconnect first` before the body is read.
        - `enable: false` (or omitted) removes the proxy and ignores `proxy_url`.
        - `enable: true` requires `proxy_url` with scheme `http`, `https` or `socks5`, for example `socks5://user:pass@host:1080`.
        - The URL is stored as given (credentials included) and echoed in `data.ProxyURL`.
        - `RequiresReconnection` and `Message` exist for a connected client, but connected clients are rejected up front, so `RequiresReconnection` is `false` in practice.
        - A session with a mobile network route (`/session/network-route`) egresses through that route instead of this proxy.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionProxyRequest"
            examples:
              socks5:
                summary: Enable a SOCKS5 proxy with credentials
                description: Stores the SOCKS5 URL; it is used on the next `POST /session/connect`.
                value:
                  enable: true
                  proxy_url: socks5://proxyuser:proxypass@proxy.example.com:1080
              http:
                summary: Enable an HTTP proxy
                description: HTTP and HTTPS proxies are accepted as well.
                value:
                  enable: true
                  proxy_url: http://proxy.example.com:3128
              disable:
                summary: Remove the proxy
                description: Clears the stored proxy; the next connect goes out directly.
                value:
                  enable: false
      responses:
        "200":
          description: Proxy stored or removed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionProxyResponse"
              examples:
                enabled:
                  summary: Proxy stored
                  value:
                    code: 200
                    data:
                      Details: Proxy configured successfully
                      ProxyURL: socks5://proxyuser:proxypass@proxy.example.com:1080
                      RequiresReconnection: false
                    success: true
                disabled:
                  summary: Proxy removed
                  value:
                    code: 200
                    data:
                      Details: Proxy disabled successfully
                      RequiresReconnection: false
                    success: true
        "400":
          description: "Bad Request: client connected, invalid body, missing or malformed `proxy_url`, or unsupported scheme."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                connected:
                  summary: Client is connected
                  value:
                    code: 400
                    error: cannot set proxy while connected. Please disconnect first
                    success: false
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_proxy_url:
                  summary: enable is true but proxy_url is empty
                  value:
                    code: 400
                    error: missing proxy_url in payload
                    success: false
                invalid_format:
                  summary: proxy_url cannot be parsed
                  value:
                    code: 400
                    error: invalid proxy URL format
                    success: false
                unsupported_scheme:
                  summary: Scheme other than http, https or socks5
                  value:
                    code: 400
                    error: only HTTP, HTTPS and SOCKS5 proxies are supported
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the proxy could not be written to the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                save_failed:
                  summary: Proxy could not be stored
                  value:
                    code: 500
                    error: failed to save proxy configuration
                    success: false
                remove_failed:
                  summary: Proxy could not be removed
                  value:
                    code: 500
                    error: failed to remove proxy configuration
                    success: false
  /session/s3/config:
    post:
      tags:
        - Session S3
      summary: Configure S3 storage for this instance
      description: |
        Stores the per-instance S3-compatible storage (AWS S3, MinIO, Backblaze B2, Cloudflare R2, etc.) used to upload
        media from incoming and outgoing messages, and chooses how that media is delivered inside events.

        **Rules:**
        - **Full replace:** every call overwrites all S3 columns. Omitted fields are stored empty, `false` or `0`, so always send the complete configuration.
        - `media_delivery` accepts `base64`, `s3`, `url` (alias, stored as `s3`) or `both`. Empty or omitted is stored as `base64`; any other value returns **400**.
        - `retention_days: 0` disables object expiration.
        - No connectivity or credential check happens here. Use `POST /session/s3/test` after saving.
        - `enabled: false` keeps the other values as sent but stops uploads for this instance.

        **Side effects:**
        - The user configuration cache is reloaded and the S3 client of the instance is rebuilt from the saved values.

        **Use Cases:**
        - Deliver media as short URLs instead of large base64 blobs in webhooks
        - Keep a copy of every media file in your own bucket with an expiration policy
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionS3ConfigRequest"
            examples:
              aws_s3:
                summary: AWS S3 with bucket owner enforced ownership
                description: Standard AWS endpoint (leave `endpoint` empty), ACLs disabled, media delivered as both URL and base64, objects kept for 30 days.
                value:
                  enabled: true
                  endpoint: ""
                  region: us-east-1
                  bucket: zuckzapgo-media-example
                  access_key: AKIAIOSFODNN7EXAMPLE
                  secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
                  path_style: false
                  public_url: ""
                  media_delivery: both
                  retention_days: 30
                  disable_acl: true
              minio_cdn:
                summary: MinIO with path-style addressing and a CDN URL
                description: Self-hosted MinIO requires `path_style`. Links in events use `public_url` as the base and objects never expire.
                value:
                  enabled: true
                  endpoint: https://minio.example.com
                  region: us-east-1
                  bucket: whatsapp-media
                  access_key: minio-access-example
                  secret_key: minio-secret-example
                  path_style: true
                  public_url: https://cdn.example.com
                  media_delivery: url
                  retention_days: 0
                  disable_acl: false
              disable:
                summary: Disable S3 uploads
                description: Because the call is a full replace, every other S3 field is stored empty and media delivery falls back to `base64`.
                value:
                  enabled: false
      responses:
        "200":
          description: S3 configuration saved and the S3 client rebuilt.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionS3ConfigSaveResponse"
              example:
                code: 200
                data:
                  Details: S3 configuration saved successfully
                  Enabled: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `media_delivery` is not an accepted value."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                invalid_media_delivery:
                  summary: media_delivery is not base64, s3, url or both
                  value:
                    code: 400
                    error: media_delivery must be 'base64', 's3', 'url', or 'both'
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the database update failed, or the cache/S3 client could not be rebuilt from the saved values."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                save_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to save S3 configuration
                    success: false
                cache_failed:
                  summary: Cache reload or S3 client rebuild failed
                  value:
                    code: 500
                    error: failed to update S3 cache
                    success: false
    get:
      tags:
        - Session S3
      summary: Get the S3 storage configuration
      description: |
        Returns the S3 storage configuration stored for this instance, as used for media uploads and media delivery in events.

        **Rules:**
        - `secret_key` is never returned.
        - `access_key` is always returned as `***`, even when no key is stored.
        - Values are read from the database, so this reflects the last `POST /session/s3/config` or `DELETE /session/s3/config`, not a connectivity check.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored S3 configuration with the access key masked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionS3ConfigGetResponse"
              examples:
                configured:
                  summary: S3 configured
                  value:
                    code: 200
                    data:
                      enabled: true
                      endpoint: https://minio.example.com
                      region: us-east-1
                      bucket: whatsapp-media
                      access_key: "***"
                      path_style: true
                      public_url: https://cdn.example.com
                      media_delivery: s3
                      retention_days: 30
                      disable_acl: false
                    success: true
                not_configured:
                  summary: S3 never configured or deleted
                  value:
                    code: 200
                    data:
                      enabled: false
                      endpoint: ""
                      region: ""
                      bucket: ""
                      access_key: "***"
                      path_style: true
                      public_url: ""
                      media_delivery: base64
                      retention_days: 30
                      disable_acl: false
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get S3 configuration
                success: false
    delete:
      tags:
        - Session S3
      summary: Delete the S3 storage configuration
      description: |
        Removes the S3 storage configuration of this instance and returns media delivery to inline `base64`.

        **Rules:**
        - Stores `enabled=false`, empty endpoint, region, bucket, keys and public URL, `path_style=true`, `media_delivery=base64` and `retention_days=30`.
        - `disable_acl` is not reset and keeps its previous value.
        - Objects already uploaded to the bucket are not deleted.
        - The user configuration cache is reloaded and the instance S3 client is removed.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: S3 configuration removed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionS3ConfigDeleteResponse"
              example:
                code: 200
                data:
                  Details: S3 configuration deleted successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the database update failed, or the cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                delete_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to delete S3 configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update S3 cache
                    success: false
  /session/s3/test:
    post:
      tags:
        - Session S3
      summary: Test the stored S3 connection
      description: |
        Verifies that the stored S3 configuration can reach the bucket, by rebuilding the S3 client and listing at most one object.

        **Rules:**
        - No request body: the test always uses the configuration saved with `POST /session/s3/config`.
        - Fails with **400** when S3 is disabled for this instance.
        - The bucket listing has a 10 second timeout; nothing is uploaded or deleted.
        - A successful test also leaves the rebuilt S3 client active for uploads.

        **Use Cases:**
        - Validate credentials, endpoint and bucket permissions right after saving them
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Bucket reachable with the stored credentials.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionS3TestResponse"
              example:
                code: 200
                data:
                  Details: S3 connection test successful
                  Bucket: whatsapp-media
                  Region: us-east-1
                success: true
        "400":
          description: "Bad Request: S3 is not enabled for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: S3 is not enabled for this user
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration could not be read, the S3 client could not be built, or the bucket listing failed (credentials, permissions, endpoint or network)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                read_failed:
                  summary: Configuration could not be read
                  value:
                    code: 500
                    error: failed to get S3 configuration
                    success: false
                client_failed:
                  summary: S3 client could not be built
                  value:
                    code: 500
                    error: "failed to initialize S3 client: failed to load AWS configuration"
                    success: false
                access_denied:
                  summary: Bucket listing rejected
                  value:
                    code: 500
                    error: "S3 connection test failed: operation error S3: ListObjectsV2, https response error StatusCode: 403, api error AccessDenied: Access Denied"
                    success: false
  /session/rabbitmq/config:
    post:
      tags:
        - Session RabbitMQ
      summary: Configure the RabbitMQ publisher for this instance
      description: |
        Stores the per-instance RabbitMQ publisher that receives WhatsApp events, including exchange and queue topology,
        dead-lettering, queue limits and publisher tuning.

        **Rules:**
        - **Full replace:** every call overwrites all RabbitMQ columns. Omitted fields are stored as `false`, empty or NULL (server defaults), so always send the complete configuration.
        - `events` is a comma-separated list of canonical, case-sensitive event names or `All`. Spaces around names are trimmed and empty items skipped; an unknown name returns **400**. Empty means no events.
        - When `enabled` is `true`: `exchange_type` defaults to `topic`, `queue_type` to `classic`, `exchange` to `zuckzapgo.user.<userID>`, `queue` to `zuckzapgouser.<userID>.events` and `routing_key` to `events.#`. `exchange_type` and `queue_type` are only validated in this case.
        - `delivery_mode` `0` or omitted is stored as `2` (persistent).
        - `connection_pool_size`, `worker_count`, `queue_buffer_size`, `batch_size` and `publish_timeout_ms` must be greater than 0; `batch_timeout_ms`, `max_retries` and `retry_delay_ms` must be non-negative.
        - `queue_arguments` and `exchange_arguments` are **JSON-encoded strings**. Sending a JSON object in these fields fails with `400 could not decode payload`.
        - The broker is not contacted here: a wrong URL does not fail this request. Use `POST /session/rabbitmq/test`.

        **Features:**
        - **Dynamic queues:** `{user_id}` and `{event_type}` placeholders in `queue` and `routing_key` are expanded per event; a `queue` containing `{event_type}` creates one queue per event type.
        - **Dead-lettering and limits:** `dead_letter_exchange`, `dead_letter_routing_key`, `message_ttl`, `max_length` and `max_length_bytes` become `x-*` queue arguments.
        - **Transport registration:** enabling registers the individual RabbitMQ transport in the event dispatcher; disabling unregisters it.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionRabbitMQConfigRequest"
            examples:
              minimal:
                summary: Enable with server defaults
                description: Only the URL and events are sent; exchange, queue, routing key, exchange type and queue type get their defaults.
                value:
                  enabled: true
                  url: amqp://zzg_user:zzg_pass@rabbitmq.example.com:5672/
                  events: All
              dynamic_quorum_dlx:
                summary: Per-event quorum queues with dead-lettering and tuning
                description: One quorum queue per event type, a dead-letter exchange, a 24 hour message TTL, overflow rejection and custom publisher tuning.
                value:
                  enabled: true
                  url: amqps://zzg_user:zzg_pass@rabbitmq.example.com:5671/whatsapp
                  exchange: whatsapp.events
                  exchange_type: topic
                  queue: user_{user_id}_event_{event_type}
                  queue_type: quorum
                  routing_key: whatsapp.{user_id}.{event_type}
                  events: Message,ReadReceipt,Connected
                  durable: true
                  auto_delete: false
                  exclusive: false
                  no_wait: false
                  delivery_mode: 2
                  dead_letter_exchange: dlx.zuckzapgo
                  dead_letter_routing_key: dlq.events
                  message_ttl: 86400000
                  max_length: 100000
                  max_length_bytes: 104857600
                  queue_arguments: '{"x-overflow":"reject-publish"}'
                  exchange_arguments: "{}"
                  worker_count: 50
                  batch_size: 500
                  batch_timeout_ms: 100
                  publish_timeout_ms: 5000
                  max_retries: 3
                  retry_delay_ms: 1000
              disable:
                summary: Disable publishing
                description: Because the call is a full replace, the stored URL, exchange, queue and events are cleared and the transport is unregistered.
                value:
                  enabled: false
      responses:
        "200":
          description: RabbitMQ configuration saved and the publisher cache rebuilt.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRabbitMQConfigSaveResponse"
              example:
                code: 200
                data:
                  Details: RabbitMQ configuration saved successfully
                  Enabled: true
                success: true
        "400":
          description: "Bad Request: invalid JSON (including JSON objects in `queue_arguments`/`exchange_arguments`), an unknown event name, an invalid exchange/queue type, or a tuning value out of range."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON or a field has the wrong type
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                unsupported_event_type:
                  summary: Unknown event name in events
                  value:
                    code: 400
                    error: "unsupported event type: MessageReceived"
                    success: false
                invalid_exchange_type:
                  summary: exchange_type is not topic, direct, fanout or headers
                  value:
                    code: 400
                    error: "invalid exchange_type: fan-out. Must be one of: topic, direct, fanout, headers"
                    success: false
                invalid_queue_type:
                  summary: queue_type is not classic, quorum or stream
                  value:
                    code: 400
                    error: "invalid queue_type: lazy. Must be one of: classic, quorum, stream"
                    success: false
                positive_value_required:
                  summary: A tuning value that must be positive is 0 or negative
                  value:
                    code: 400
                    error: worker_count must be greater than 0
                    success: false
                non_negative_value_required:
                  summary: A tuning value that must be non-negative is negative
                  value:
                    code: 400
                    error: max_retries must be non-negative
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the database update failed, or the cache/publisher could not be rebuilt."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                save_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to save RabbitMQ configuration
                    success: false
                cache_failed:
                  summary: Cache reload or publisher rebuild failed
                  value:
                    code: 500
                    error: failed to update RabbitMQ cache
                    success: false
    get:
      tags:
        - Session RabbitMQ
      summary: Get the RabbitMQ configuration and queue statistics
      description: |
        Returns the stored RabbitMQ publisher configuration of this instance together with the dynamic queues this process has declared.

        **Rules:**
        - The password inside `config.url` is replaced by `***`.
        - Tuning fields (`connection_pool_size`, `worker_count`, ...) are only present when a value is stored.
        - `dead_letter_*`, `queue_arguments` and `exchange_arguments` are only present when non-empty; `message_ttl`, `max_length` and `max_length_bytes` only when greater than 0.
        - `stats` is in-memory state of this process (reset on restart) and is `null` when the RabbitMQ manager is not running.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored configuration (URL password masked) and in-memory queue statistics.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRabbitMQConfigGetResponse"
              example:
                code: 200
                data:
                  config:
                    enabled: true
                    url: "amqps://zzg_user:***@rabbitmq.example.com:5671/whatsapp"
                    exchange: whatsapp.events
                    exchange_type: topic
                    queue: user_{user_id}_event_{event_type}
                    queue_type: quorum
                    routing_key: whatsapp.{user_id}.{event_type}
                    events: Message,ReadReceipt,Connected
                    durable: true
                    auto_delete: false
                    exclusive: false
                    no_wait: false
                    delivery_mode: 2
                    worker_count: 50
                    batch_size: 500
                    dead_letter_exchange: dlx.zuckzapgo
                    dead_letter_routing_key: dlq.events
                    message_ttl: 86400000
                    queue_arguments: '{"x-overflow":"reject-publish"}'
                  stats:
                    userID: 4e4942c7dee1deef99ab8fd9f7350de5
                    dynamicQueues: true
                    createdQueues: 2
                    queueNames:
                      - user_4e4942c7dee1deef99ab8fd9f7350de5_event_Message
                      - user_4e4942c7dee1deef99ab8fd9f7350de5_event_ReadReceipt
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get RabbitMQ configuration
                success: false
    delete:
      tags:
        - Session RabbitMQ
      summary: Delete the RabbitMQ configuration
      description: |
        Disables RabbitMQ publishing for this instance, clears the stored broker settings and unregisters the individual RabbitMQ transport.

        **Rules:**
        - Stores `enabled=false`, empty URL, exchange, exchange type, queue, queue type, routing key and events, `durable=true`, `auto_delete`/`exclusive`/`no_wait=false`, `delivery_mode=2`, and NULL dead-letter, TTL, length and argument fields.
        - Publisher tuning fields (`connection_pool_size`, `worker_count`, `queue_buffer_size`, `batch_size`, timeouts and retries) are not reset.
        - Exchanges and queues already declared on the broker are not deleted.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: RabbitMQ configuration removed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRabbitMQConfigDeleteResponse"
              example:
                code: 200
                data:
                  Details: RabbitMQ configuration deleted successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the database update failed, or the cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                delete_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to delete RabbitMQ configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update RabbitMQ cache
                    success: false
  /session/rabbitmq/test:
    post:
      tags:
        - Session RabbitMQ
      summary: Test the RabbitMQ connection and publish a test message
      description: |
        Verifies the stored RabbitMQ configuration end to end: it reloads the configuration, connects to the broker,
        declares the configured topology, starts the publisher and publishes one test event.

        **Rules:**
        - No request body: the test uses the configuration saved with `POST /session/rabbitmq/config`.
        - Fails with **400** when RabbitMQ is disabled for this instance.
        - The published test event has type `test` and a payload with `test: true`, a message, a Unix timestamp and the user ID; consumers bound to the routing key receive it.
        - A successful test leaves the publisher initialized for normal event delivery.
        - The broker URL is never echoed: `data.URL` is always `***`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Broker reachable, topology declared and test message published.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRabbitMQTestResponse"
              example:
                code: 200
                data:
                  Details: RabbitMQ connection test successful
                  Exchange: whatsapp.events
                  ExchangeType: topic
                  Queue: user_{user_id}_event_{event_type}
                  QueueType: quorum
                  RoutingKey: whatsapp.{user_id}.{event_type}
                  TestMessage: Test message published successfully
                  URL: "***"
                success: true
        "400":
          description: "Bad Request: RabbitMQ is not enabled for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: RabbitMQ is not enabled for this user
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration could not be read or reloaded, the broker connection or topology declaration failed, the publisher could not start, or the test message could not be published."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                read_failed:
                  summary: Configuration could not be read
                  value:
                    code: 500
                    error: failed to get RabbitMQ configuration
                    success: false
                refresh_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: "failed to refresh RabbitMQ configuration: user config not found in cache for RabbitMQ refresh"
                    success: false
                not_cached:
                  summary: Reloaded configuration missing from the manager
                  value:
                    code: 500
                    error: failed to retrieve RabbitMQ configuration from cache
                    success: false
                connection_failed:
                  summary: Broker unreachable or credentials rejected
                  value:
                    code: 500
                    error: "RabbitMQ connection test failed: failed to connect to RabbitMQ: dial tcp 203.0.113.10:5672: connect: connection refused"
                    success: false
                init_failed:
                  summary: Publisher could not start after the test
                  value:
                    code: 500
                    error: "failed to initialize RabbitMQ client after test: failed to open channel: channel/connection is not open"
                    success: false
                publish_failed:
                  summary: Test message not published
                  value:
                    code: 500
                    error: "failed to publish test message: publish timeout"
                    success: false
  /session/skipmedia/config:
    post:
      tags:
        - Session Skips
      summary: Turn media download for events on or off
      description: |
        Controls whether this instance downloads the media (images, videos, audio, documents, stickers) of message events.
        With the flag on, events are still delivered with the WhatsApp message metadata, but without base64 content and without S3 uploads.

        **Rules:**
        - `enabled: true` skips media download; `false` restores it. An omitted `enabled` is treated as `false`.
        - Media is skipped when **any** of these is on: the `-skipmedia` command-line flag, the `GLOBAL_SKIP_MEDIA_DOWNLOAD` environment variable, or this per-instance flag. This endpoint cannot override the first two.
        - Takes effect for the next events: the user configuration cache is reloaded immediately.

        **Use Cases:**
        - High-volume instances that only need text and message metadata
        - Reducing bandwidth and S3 storage when media is fetched on demand with `/chat/download*`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkipMediaConfig"
            examples:
              skip_media:
                summary: Skip media download
                description: Events keep message metadata but carry no base64 media and nothing is uploaded to S3.
                value:
                  enabled: true
              download_media:
                summary: Download media again
                description: Media is downloaded (and uploaded to S3 when configured) unless the flag or environment variable forces the skip.
                value:
                  enabled: false
      responses:
        "200":
          description: Flag stored and cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipMediaUpdateResponse"
              example:
                code: 200
                data:
                  Details: Skip media download configuration updated successfully
                  SkipMediaDownload: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be stored or the media cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update skip media download configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update skip media cache
                    success: false
    get:
      tags:
        - Session Skips
      summary: Get the media download skip setting
      description: |
        Returns the per-instance media download flag together with the server-wide environment setting, so clients can tell why media is missing from events.

        **Rules:**
        - `UserSkipMediaDownload` is the value stored with `POST /session/skipmedia/config` (`false` when never set).
        - `GlobalSkipMediaDownload` mirrors `GLOBAL_SKIP_MEDIA_DOWNLOAD` and is read-only.
        - The `-skipmedia` command-line flag is not reported, but it also forces the skip when set.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Per-instance and server-wide media skip settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipMediaGetResponse"
              example:
                code: 200
                data:
                  UserSkipMediaDownload: true
                  GlobalSkipMediaDownload: false
                  Details: Skip media download configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get skip media download configuration
                success: false
  /session/skipgroups/config:
    post:
      tags:
        - Session Skips
      summary: Ignore or process group chat events
      description: |
        Controls whether this instance drops every event that comes from WhatsApp groups (`@g.us`), so only direct chats, newsletters and broadcasts reach webhooks and transports.

        **Rules:**
        - `enabled: true` drops group events; `false` processes them again. An omitted `enabled` is treated as `false`.
        - Dropped event types: `Message` and `Receipt` in group chats, `ChatPresence` in groups, `GroupInfo` and `JoinedGroup`.
        - Dropped events are discarded before any processing: no media download, no webhook, no RabbitMQ/SQS/Redis/WebSocket delivery.
        - The server-wide `GLOBAL_SKIP_GROUPS` environment variable drops group events for every instance, regardless of this flag.
        - Sending to groups through the API is not affected.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkipGroupsConfig"
            examples:
              ignore_groups:
                summary: Ignore group events
                description: A support bot that only answers direct chats stops receiving group traffic.
                value:
                  enabled: true
              process_groups:
                summary: Process group events again
                description: Group messages, receipts and group updates are delivered again.
                value:
                  enabled: false
      responses:
        "200":
          description: Flag stored and cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipGroupsUpdateResponse"
              example:
                code: 200
                data:
                  Details: Skip groups configuration updated successfully
                  SkipGroups: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be stored or the groups cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update skip groups configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update skip groups cache
                    success: false
    get:
      tags:
        - Session Skips
      summary: Get the group events skip setting
      description: |
        Returns whether this instance currently drops events from WhatsApp groups.

        **Rules:**
        - `SkipGroups` is the per-instance value (`false` when never set).
        - The server-wide `GLOBAL_SKIP_GROUPS` environment variable is not included in this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Per-instance group skip flag.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipGroupsGetResponse"
              example:
                code: 200
                data:
                  SkipGroups: true
                  Details: Skip groups configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get skip groups configuration
                success: false
  /session/skipnewsletters/config:
    post:
      tags:
        - Session Skips
      summary: Ignore or process newsletter (channel) events
      description: |
        Controls whether this instance drops events that come from WhatsApp newsletters (Channels, `@newsletter`).

        **Rules:**
        - `enabled: true` drops newsletter events; `false` processes them again. An omitted `enabled` is treated as `false`.
        - Dropped event types: `Message` and `Receipt` in newsletter chats, `ChatPresence` in newsletters, `NewsletterJoin`, `NewsletterLeave`, `NewsletterMuteChange` and `NewsletterLiveUpdate`.
        - Dropped events never reach media download, webhooks or any transport.
        - The server-wide `GLOBAL_SKIP_NEWSLETTERS` environment variable drops newsletter events for every instance, regardless of this flag.
        - `/newsletter/*` endpoints keep working.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkipNewslettersConfig"
            examples:
              ignore_newsletters:
                summary: Ignore channel events
                description: Followed channels can post many updates; this keeps them out of the webhook.
                value:
                  enabled: true
              process_newsletters:
                summary: Process channel events again
                description: Newsletter messages and follow/mute updates are delivered again.
                value:
                  enabled: false
      responses:
        "200":
          description: Flag stored and cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipNewslettersUpdateResponse"
              example:
                code: 200
                data:
                  Details: Skip newsletters configuration updated successfully
                  SkipNewsletters: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be stored or the newsletters cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update skip newsletters configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update skip newsletters cache
                    success: false
    get:
      tags:
        - Session Skips
      summary: Get the newsletter events skip setting
      description: |
        Returns whether this instance currently drops events from WhatsApp newsletters (Channels).

        **Rules:**
        - `SkipNewsletters` is the per-instance value (`false` when never set).
        - The server-wide `GLOBAL_SKIP_NEWSLETTERS` environment variable is not included in this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Per-instance newsletter skip flag.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipNewslettersGetResponse"
              example:
                code: 200
                data:
                  SkipNewsletters: false
                  Details: Skip newsletters configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get skip newsletters configuration
                success: false
  /session/skipbroadcasts/config:
    post:
      tags:
        - Session Skips
      summary: Ignore or process broadcast and status events
      description: |
        Controls whether this instance drops events from broadcast chats, which include contacts' Status updates (`status@broadcast`) and broadcast lists (`@broadcast`).

        **Rules:**
        - `enabled: true` drops broadcast events; `false` processes them again. An omitted `enabled` is treated as `false`.
        - Dropped event types: `Message` and `Receipt` whose chat JID contains `@broadcast`, and `ChatPresence` in those chats.
        - Dropped events never reach media download, webhooks or any transport. Skipping Status traffic also avoids downloading status media.
        - The server-wide `GLOBAL_SKIP_BROADCASTS` environment variable drops broadcast events for every instance, regardless of this flag.
        - Publishing your own status with `/status/send/*` is not affected.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkipBroadcastsConfig"
            examples:
              ignore_status:
                summary: Ignore Status and broadcast events
                description: Contacts' Status posts and their receipts stop reaching the webhook.
                value:
                  enabled: true
              process_status:
                summary: Process Status and broadcast events again
                description: Status posts and broadcast list messages are delivered again.
                value:
                  enabled: false
      responses:
        "200":
          description: Flag stored and cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipBroadcastsUpdateResponse"
              example:
                code: 200
                data:
                  Details: Skip broadcasts configuration updated successfully
                  SkipBroadcasts: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be stored or the broadcasts cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update skip broadcasts configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update skip broadcasts cache
                    success: false
    get:
      tags:
        - Session Skips
      summary: Get the broadcast and status events skip setting
      description: |
        Returns whether this instance currently drops events from broadcast chats and contacts' Status updates.

        **Rules:**
        - `SkipBroadcasts` is the per-instance value (`false` when never set).
        - The server-wide `GLOBAL_SKIP_BROADCASTS` environment variable is not included in this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Per-instance broadcast skip flag.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipBroadcastsGetResponse"
              example:
                code: 200
                data:
                  SkipBroadcasts: true
                  Details: Skip broadcasts configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get skip broadcasts configuration
                success: false
  /session/skipownmessages/config:
    post:
      tags:
        - Session Skips
      summary: Ignore or process events about your own messages
      description: |
        Controls whether this instance drops events generated by messages sent from this WhatsApp account (from the phone, other linked devices or this API).

        **Rules:**
        - `enabled: true` drops own-message events; `false` processes them again. An omitted `enabled` is treated as `false`.
        - Dropped event types: `Message` and `Receipt` with `IsFromMe`, and `DeleteForMe`, `Star` and `MediaRetry` events about messages sent by this account.
        - Dropped events never reach media download, webhooks or any transport.
        - The server-wide `GLOBAL_SKIP_OWN_MESSAGES` environment variable drops own `Message`, `Receipt` and `MediaRetry` events for every instance, regardless of this flag.
        - Sending through the API still works and still returns the message ID in the HTTP response.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkipOwnMessagesConfig"
            examples:
              ignore_own:
                summary: Ignore own messages
                description: A chatbot that must never react to its own replies stops receiving them as events.
                value:
                  enabled: true
              process_own:
                summary: Process own messages again
                description: Messages typed on the phone or sent by other devices are delivered again, useful for inbox mirroring.
                value:
                  enabled: false
      responses:
        "200":
          description: Flag stored and cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipOwnMessagesUpdateResponse"
              example:
                code: 200
                data:
                  Details: Skip own messages configuration updated successfully
                  SkipOwnMessages: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be stored or the own-messages cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update skip own messages configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update skip own messages cache
                    success: false
    get:
      tags:
        - Session Skips
      summary: Get the own-message events skip setting
      description: |
        Returns whether this instance currently drops events about messages sent by this WhatsApp account.

        **Rules:**
        - `SkipOwnMessages` is the per-instance value (`false` when never set).
        - The server-wide `GLOBAL_SKIP_OWN_MESSAGES` environment variable is not included in this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Per-instance own-message skip flag.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipOwnMessagesGetResponse"
              example:
                code: 200
                data:
                  SkipOwnMessages: false
                  Details: Skip own messages configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flag could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get skip own messages configuration
                success: false
  /session/skipcalls/config:
    post:
      tags:
        - Session Skips
      summary: Auto-reject incoming calls
      description: |
        Turns automatic rejection of incoming WhatsApp calls on or off for this instance and sets the text message sent to the caller after rejecting.

        **Rules:**
        - `enabled: true` rejects every incoming 1:1 call offer and group call notice, then sends `reject_message` to the caller as a text message. An omitted `enabled` is treated as `false`.
        - `CallOffer` and `CallOfferNotice` events are dropped (not delivered to webhooks or transports) while the flag is on.
        - `reject_message` empty or omitted is stored as `Sorry, I cannot take calls at the moment.`
        - `reject_type` accepts `busy`, `declined` or `unavailable`; empty or omitted is stored as `busy`; anything else returns **400**. The value is stored and logged only: the rejection sent to WhatsApp is the same for every type.
        - When the native calls engine owns inbound calls for the instance (`/call/config`), the engine handles the call and no automatic rejection or message is sent.
        - The server-wide `GLOBAL_SKIP_CALLS` environment variable also rejects calls and drops `CallOffer` events for every instance, regardless of this flag.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkipCallsConfig"
            examples:
              reject_with_message:
                summary: Reject calls and reply with a custom text
                description: Every incoming call is rejected and the caller receives the message as a WhatsApp text.
                value:
                  enabled: true
                  reject_message: Sorry, I cannot take calls right now. Please send a message.
                  reject_type: busy
              reject_with_defaults:
                summary: Reject calls with the default text
                description: Omitting `reject_message` and `reject_type` stores the default message and `busy`.
                value:
                  enabled: true
              accept_calls:
                summary: Stop rejecting calls
                description: Calls ring normally again and call events are delivered. The stored message and type are overwritten with the defaults.
                value:
                  enabled: false
      responses:
        "200":
          description: Settings stored and cache reloaded. `data` echoes the stored values, including defaults.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipCallsUpdateResponse"
              example:
                code: 200
                data:
                  Details: Skip calls configuration updated successfully
                  SkipCalls: true
                  RejectMessage: Sorry, I cannot take calls right now. Please send a message.
                  RejectType: busy
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `reject_type` is not an accepted value."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                invalid_reject_type:
                  summary: reject_type is not busy, declined or unavailable
                  value:
                    code: 400
                    error: "invalid reject_type, must be: busy, declined, or unavailable"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the settings could not be stored or the calls cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update skip calls configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update skip calls cache
                    success: false
    get:
      tags:
        - Session Skips
      summary: Get the call auto-reject settings
      description: |
        Returns whether incoming calls are automatically rejected for this instance and which message is sent to the caller.

        **Rules:**
        - Defaults are returned when nothing is stored: `SkipCalls=false`, `RejectMessage=Sorry, I cannot take calls at the moment.`, `RejectType=busy`.
        - The server-wide `GLOBAL_SKIP_CALLS` environment variable is not included in this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored auto-reject settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionSkipCallsGetResponse"
              example:
                code: 200
                data:
                  SkipCalls: true
                  RejectMessage: Sorry, I cannot take calls right now. Please send a message.
                  RejectType: busy
                  Details: Skip calls configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the settings could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get skip calls configuration
                success: false
  /session/globaltransports/config:
    post:
      tags:
        - Session Global Transports
      summary: Opt this instance out of global event transports
      operationId: configureGlobalTransportSkips
      description: |
        Sets per-instance flags that make the server-wide (global) event dispatcher skip this instance for selected transports:
        the global webhook, RabbitMQ, SQS, Redis, WebSocket and the shared S3 media enrichment.

        **Rules:**
        - **Partial update:** only the flags present in the body are written; omitted flags keep their stored value.
        - At least one flag is required; `{}` or a body with only unknown fields returns **400** `no fields provided`.
        - Unknown fields are ignored. A non-boolean flag value or invalid JSON returns **400** `invalid request body`.
        - Only the global dispatcher is affected. The instance's own webhook (`/webhook`) and RabbitMQ (`/session/rabbitmq/config`) keep working.
        - The response is built from the reloaded cache. If the cache reload fails after the database update, the request still returns **200** and the flags apply once the cache is refreshed.

        **Use Cases:**
        - Keep one tenant's traffic out of a shared global RabbitMQ or SQS queue
        - Stop uploading one instance's media to the global S3 bucket
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GlobalTransportSkipsRequest"
            examples:
              skip_rabbitmq_and_s3:
                summary: Opt out of global RabbitMQ and S3
                description: Only these two flags change; webhook, SQS, Redis and WebSocket keep their stored values.
                value:
                  skipGlobalRabbitMQ: true
                  skipGlobalS3: true
              restore_webhook:
                summary: Receive the global webhook again
                description: Clears a single flag without touching the others.
                value:
                  skipGlobalWebhook: false
              set_all_flags:
                summary: Set every flag explicitly
                description: Useful to replicate a known configuration across instances.
                value:
                  skipGlobalWebhook: false
                  skipGlobalRabbitMQ: true
                  skipGlobalSQS: false
                  skipGlobalRedis: false
                  skipGlobalWebSocket: false
                  skipGlobalS3: true
      responses:
        "200":
          description: Flags stored. `data` contains all six flags after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GlobalTransportSkipsResponse"
              example:
                code: 200
                data:
                  skipGlobalWebhook: false
                  skipGlobalRabbitMQ: true
                  skipGlobalSQS: false
                  skipGlobalRedis: false
                  skipGlobalWebSocket: false
                  skipGlobalS3: true
                  details: Global transport skip configuration updated successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, a flag is not a boolean, or no known flag was sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_request_body:
                  summary: Invalid JSON or non-boolean flag
                  value:
                    code: 400
                    error: invalid request body
                    success: false
                no_fields_provided:
                  summary: No known flag in the body
                  value:
                    code: 400
                    error: no fields provided
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the flags could not be stored."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to update transport configuration
                success: false
    get:
      tags:
        - Session Global Transports
      summary: Get the global transport opt-out flags
      operationId: getGlobalTransportSkips
      description: |
        Returns the six per-instance flags that the global event dispatcher checks before delivering this instance's events to each global transport.

        **Rules:**
        - Values come from the user configuration cache; on a cache miss the configuration is loaded from the database first.
        - A flag that was never set is returned as `false` (the instance receives that global transport).
        - Whether each global transport is enabled at all is a server setting and is not part of this response.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Current opt-out flags of this instance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GlobalTransportSkipsResponse"
              example:
                code: 200
                data:
                  skipGlobalWebhook: false
                  skipGlobalRabbitMQ: true
                  skipGlobalSQS: false
                  skipGlobalRedis: false
                  skipGlobalWebSocket: false
                  skipGlobalS3: true
                  details: Global transport skip configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration was not cached and could not be loaded from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to load user configuration
                success: false
  /session/presence/config:
    post:
      tags:
        - Session Presence
      summary: Turn automatic "unavailable" presence on or off
      operationId: configureAutoPresenceUnavailable
      description: |
        Controls whether this instance marks itself as unavailable right after connecting, so the phone keeps receiving push notifications while the API session stays online.

        **Rules:**
        - `enabled: true`: after every connection (and after the initial app-state sync) the gateway sends the protocol-required `available` presence, waits 3 seconds, then sends `unavailable`.
        - `enabled: false` (default, also used when `enabled` is omitted): the instance stays `available` after connecting, which can silence notifications on the phone.
        - The setting applies from the next connection or app-state sync; it does not change the current presence immediately.
        - Manual presence changes with `POST /user/presence` are not affected.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionPresenceConfigRequest"
            examples:
              keep_phone_notifications:
                summary: Go unavailable 3 seconds after connecting
                description: Recommended when the same account is also used on the phone and notifications must keep arriving.
                value:
                  enabled: true
              stay_available:
                summary: Stay available after connecting
                description: Restores the default behavior.
                value:
                  enabled: false
      responses:
        "200":
          description: Setting stored and presence cache reloaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPresenceConfigUpdateResponse"
              example:
                code: 200
                data:
                  Details: Auto presence unavailable configuration updated successfully
                  AutoPresenceUnavailable: true
                success: true
        "400":
          description: "Bad Request: the body is empty or not valid JSON, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the setting could not be stored or the presence cache could not be reloaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_failed:
                  summary: Database update failed
                  value:
                    code: 500
                    error: failed to update auto presence unavailable configuration
                    success: false
                cache_failed:
                  summary: Cache reload failed
                  value:
                    code: 500
                    error: failed to update presence cache
                    success: false
    get:
      tags:
        - Session Presence
      summary: Get the automatic "unavailable" presence setting
      operationId: getAutoPresenceUnavailableConfig
      description: |
        Returns whether this instance sends an `unavailable` presence 3 seconds after connecting.

        **Rules:**
        - `AutoPresenceUnavailable` is `false` when the setting was never stored.
        - This reports the stored setting, not the presence currently shown to contacts.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored setting.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionPresenceConfigGetResponse"
              example:
                code: 200
                data:
                  AutoPresenceUnavailable: true
                  Details: Auto presence unavailable configuration retrieved successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the setting could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: failed to get auto presence unavailable configuration
                success: false
  /user/info:
    post:
      tags:
        - User
      summary: Get user information
      description: |
        Fetches WhatsApp profile data for one or more accounts: status (about) text, profile picture ID, business verification, LID and the list of devices currently registered to each account.

        **Rules:**
        - Send the numbers in `phone`, or in the legacy `Phone` field. Both accept a single string or an array of strings; `phone` is read first and `Phone` only when `phone` yields no value.
        - Each value may be a phone number with country code (a leading `+` is stripped) or a user JID. The first unparseable value aborts the request with **400**.
        - Non-string items inside an array are ignored.
        - A missing WhatsApp session returns **500** `no session`; a failed WhatsApp query returns **500**.

        **Response:**
        - `data.users` is an object keyed by JID. `verified_name`, `business_name`, `lid` and `first_device` are omitted when empty.
        - `verified_certificate` only appears for business accounts that carry a certificate.
        - `devices[].device` is the platform name derived from the device ID (see `POST /device/platform`).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserInfoRequest"
            examples:
              single:
                summary: One phone number
                description: A single number sent as a plain string.
                value:
                  phone: "5511999999999"
              several:
                summary: Several phone numbers
                description: Up to the WhatsApp usync limits; every value must be a valid number or JID.
                value:
                  phone:
                    - "5511999999999"
                    - "5511988888888"
              legacy_field:
                summary: Legacy Phone field with a JID
                description: The capitalized `Phone` field is still accepted for backward compatibility.
                value:
                  Phone:
                    - 5511999999999@s.whatsapp.net
      responses:
        "200":
          description: Profile data keyed by JID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserInfoResponse"
              examples:
                personal_account:
                  summary: Personal account with two devices
                  value:
                    code: 200
                    data:
                      users:
                        5511999999999@s.whatsapp.net:
                          jid: 5511999999999@s.whatsapp.net
                          status: Available
                          picture_id: "1700000000"
                          lid: 123456789012345@lid
                          is_business_account: false
                          device_count: 2
                          first_device: 5511999999999@s.whatsapp.net
                          devices:
                            - user: "5511999999999"
                              agent: "0"
                              device: UNKNOWN
                              server: s.whatsapp.net
                              ad: 5511999999999.0:0@s.whatsapp.net
                              jid: 5511999999999@s.whatsapp.net
                            - user: "5511999999999"
                              agent: "0"
                              device: SAFARI
                              server: s.whatsapp.net
                              ad: 5511999999999.0:5@s.whatsapp.net
                              jid: 5511999999999:5@s.whatsapp.net
                    success: true
                business_account:
                  summary: Verified business account
                  value:
                    code: 200
                    data:
                      users:
                        5511988888888@s.whatsapp.net:
                          jid: 5511988888888@s.whatsapp.net
                          status: Open Monday to Friday
                          picture_id: "1728656243"
                          verified_name: Example Store
                          business_name: Example Store
                          is_business_account: true
                          device_count: 1
                          first_device: 5511988888888@s.whatsapp.net
                          verified_certificate:
                            has_signature: true
                            has_server_signature: true
                          devices:
                            - user: "5511988888888"
                              agent: "0"
                              device: UNKNOWN
                              server: s.whatsapp.net
                              ad: 5511988888888.0:0@s.whatsapp.net
                              jid: 5511988888888@s.whatsapp.net
                    success: true
        "400":
          description: "Bad Request: invalid JSON, no phone value, a field with the wrong type, or an unparseable number."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: "could not decode payload: unexpected EOF"
                    success: false
                phone_wrong_type:
                  summary: phone is neither a string nor an array
                  value:
                    code: 400
                    error: phone field must be string or array of strings
                    success: false
                legacy_phone_wrong_type:
                  summary: Phone is neither a string nor an array
                  value:
                    code: 400
                    error: Phone field must be string or array of strings
                    success: false
                missing_phone:
                  summary: Neither phone nor Phone provided
                  value:
                    code: 400
                    error: missing phone numbers in payload. Use 'Phone' or 'phone' field
                    success: false
                invalid_phone:
                  summary: A value is not a valid number or JID
                  value:
                    code: 400
                    error: "invalid phone number: @s.whatsapp.net"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the WhatsApp user-info query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "Failed to get user info: websocket not connected"
                    success: false
  /user/privacy/settings:
    get:
      tags:
        - User
      summary: Get privacy settings fresh from WhatsApp
      description: |
        Fetches the privacy settings of the connected account directly from WhatsApp, bypassing the library cache, and returns them as flat fields.

        **Rules:**
        - No query parameters or body. The cache is bypassed on every call.
        - Unlike `GET /privacy/settings`, a failed fetch is reported as **500** instead of returning empty values.

        **Values:**
        - `group_add`, `last_seen`, `status`, `profile`: `all`, `contacts`, `contact_blacklist` or `none`.
        - `read_receipts`: `all` or `none`. `online`: `all` or `match_last_seen`. `call_add`: `all` or `known`.
        - A field is an empty string when WhatsApp did not report it.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Current privacy settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserPrivacySettingsResponse"
              example:
                code: 200
                data:
                  group_add: contacts
                  last_seen: contacts
                  status: contacts
                  profile: all
                  read_receipts: all
                  online: match_last_seen
                  call_add: all
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the privacy query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                fetch_failed:
                  summary: WhatsApp privacy query failed
                  value:
                    code: 500
                    error: "failed to get privacy settings: websocket not connected"
                    success: false
  /user/pushname:
    post:
      tags:
        - User
      summary: Change your push name
      description: |
        Changes the push name (display name) of the connected account, the name recipients see next to your number when you are not in their contacts.

        **Rules:**
        - `push_name` is required and cannot be empty.
        - The change is sent as an app-state `setting_pushName` mutation. On sync conflicts (409 or LTHash mismatch) the gateway runs a full app-state sync and retries up to 3 times before failing.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangePushNameRequest"
            examples:
              company_name:
                summary: Company display name
                description: A short brand name shown to people who have not saved your number.
                value:
                  push_name: Example Store
              agent_name:
                summary: Personal display name
                description: Any non-empty text is accepted; emoji are allowed.
                value:
                  push_name: Maria from Support
      responses:
        "200":
          description: Push name updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserPushNameResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Push name updated successfully
                  push_name: Example Store
                success: true
        "400":
          description: "Bad Request: invalid JSON or empty `push_name`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                empty_push_name:
                  summary: push_name missing or empty
                  value:
                    code: 400
                    error: push name cannot be empty
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the app-state update failed after the recovery attempts."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: App-state update failed
                  value:
                    code: 500
                    error: "failed to change push name: app state error (not recoverable): websocket not connected"
                    success: false
  /user/contact/info:
    post:
      tags:
        - User
      summary: Get combined contact information
      description: |
        Builds one consolidated view of a contact from several WhatsApp sources: registration check, user info (status, devices), profile picture (downloaded and embedded as base64), saved contact names, LID mapping and business profile.

        **Rules:**
        - `phone` is required: a phone number, a user JID or a LID (`@lid`).
        - A LID is first translated to the phone number using the local LID store; when no mapping is cached the request returns **404**.
        - If the number is not on WhatsApp, the call still returns **200** with `is_on_whatsapp: false` and the lookup sections set to `null`.
        - The registration check is mandatory (**500** when it fails); user info, avatar, contact names and business profile failures only null their section.
        - The avatar lookup has a 10-second timeout and is skipped when WhatsApp reports no picture.

        **Response:**
        - `input_type` tells how `phone` was interpreted: `phone`, `jid` or `lid`.
        - `jid` and `phone` are always phone-shaped; `lid` is the contact LID when known.
        - `avatar.base64` is present only when the gateway could download the picture; `preview_url` only when both sizes were fetched.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserContactInfoRequest"
            examples:
              phone_number:
                summary: Phone number
                description: Phone number with country code, without `+`.
                value:
                  phone: "5511999999999"
              jid_format:
                summary: User JID
                description: A full phone JID; `input_type` becomes `jid`.
                value:
                  phone: 5511999999999@s.whatsapp.net
              lid_format:
                summary: LID
                description: Requires a cached LID-to-phone mapping; `input_type` becomes `lid`.
                value:
                  phone: 123456789012345@lid
      responses:
        "200":
          description: Consolidated contact information.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserContactInfoResponse"
              examples:
                business_contact:
                  summary: Registered business contact
                  value:
                    code: 200
                    data:
                      phone: "5511988888888"
                      jid: 5511988888888@s.whatsapp.net
                      input_type: phone
                      lid: 123456789012346@lid
                      is_on_whatsapp: true
                      verified_name: Example Store
                      user_info:
                        status: Open Monday to Friday
                        picture_id: "1728656243"
                        verified_name: Example Store
                        is_business_account: true
                        device_count: 1
                      devices:
                        - user: "5511988888888"
                          agent: "0"
                          device: UNKNOWN
                          server: s.whatsapp.net
                          ad: 5511988888888.0:0@s.whatsapp.net
                          jid: 5511988888888@s.whatsapp.net
                      avatar:
                        url: https://pps.whatsapp.net/v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                        id: "1728656243"
                        type: image
                        direct_path: /v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                        base64: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2Q==
                        width: 640
                        height: 640
                        preview_url: https://pps.whatsapp.net/v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?stp=dst-jpg_s96x96&ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                        preview_direct_path: /v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?stp=dst-jpg_s96x96&ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                      contact_names:
                        first_name: Example
                        full_name: Example Store
                        push_name: Example Store
                        business_name: Example Store
                      business_profile:
                        address: 100 Example Avenue, Sao Paulo
                        email: contact@example.com
                        business_hours_timezone: America/Sao_Paulo
                        categories:
                          - id: "133436743388217"
                            name: Shopping & Retail
                        profile_options:
                          commerce_experience: catalog
                        business_hours:
                          - day_of_week: mon
                            mode: specific_hours
                            open_time: "09:00"
                            close_time: "18:00"
                    success: true
                not_on_whatsapp:
                  summary: Number without WhatsApp
                  value:
                    code: 200
                    data:
                      phone: "5511977777777"
                      jid: 5511977777777@s.whatsapp.net
                      input_type: phone
                      is_on_whatsapp: false
                      lid: null
                      user_info: null
                      devices: null
                      avatar: null
                      contact_names: null
                      business_profile: null
                    success: true
        "400":
          description: "Bad Request: invalid JSON, `phone` missing, or `phone` is not a valid number/JID/LID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone:
                  summary: phone missing
                  value:
                    code: 400
                    error: missing phone in payload
                    success: false
                invalid_phone:
                  summary: phone cannot be parsed
                  value:
                    code: 400
                    error: invalid phone number, JID, or LID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: the LID has no cached phone mapping on this gateway."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: could not resolve LID to phone number. The LID mapping may not be cached locally
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, LID store unavailable, or the registration check failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                lid_store_unavailable:
                  summary: LID input but the LID store is not initialized
                  value:
                    code: 500
                    error: LID store not available
                    success: false
                check_failed:
                  summary: Registration check failed
                  value:
                    code: 500
                    error: "failed to check WhatsApp registration: websocket not connected"
                    success: false
  /user/contact/add:
    post:
      tags:
        - User
      summary: Add or rename a contact
      description: |
        Saves a contact name in the account address book, or renames an existing contact. The change is synced to the phone and every linked device through app state.

        **Rules:**
        - `phone` and `full_name` are required and cannot be empty.
        - `phone` accepts a phone number with country code or a user JID.
        - On sync conflicts (409 or LTHash mismatch) the gateway runs a full app-state sync and retries up to 3 times before failing with **500**.

        **Use Cases:**
        - Keep WhatsApp contact names aligned with a CRM
        - Save a lead so their profile photo and status become visible under "My contacts" privacy
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserContactAddRequest"
            examples:
              new_contact:
                summary: Save a new contact
                description: Adds the number to the address book with the given name.
                value:
                  phone: "5511999999999"
                  full_name: Maria Silva
              rename_by_jid:
                summary: Rename an existing contact by JID
                description: Sending an already saved contact replaces its name.
                value:
                  phone: 5511988888888@s.whatsapp.net
                  full_name: Example Store - Sales
      responses:
        "200":
          description: Contact saved and synced.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserContactAddResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Contact added successfully
                  phone: "5511999999999"
                  full_name: Maria Silva
                success: true
        "400":
          description: "Bad Request: invalid JSON, a required field is empty, or `phone` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                empty_phone:
                  summary: phone missing or empty
                  value:
                    code: 400
                    error: phone number cannot be empty
                    success: false
                empty_full_name:
                  summary: full_name missing or empty
                  value:
                    code: 400
                    error: full name cannot be empty
                    success: false
                invalid_phone:
                  summary: phone cannot be parsed
                  value:
                    code: 400
                    error: could not parse phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the app-state update failed after the recovery attempts."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: App-state update failed
                  value:
                    code: 500
                    error: "failed to add contact: app state error (not recoverable): websocket not connected"
                    success: false
  /user/contact/remove:
    post:
      tags:
        - User
      summary: Remove a saved contact
      description: |
        Deletes a contact from the account address book. The removal is synced to the phone and every linked device; chats and messages are kept.

        **Rules:**
        - `phone` is required: a phone number with country code or a user JID.
        - Removing a number that is not saved is forwarded to WhatsApp as is.
        - On sync conflicts (409 or LTHash mismatch) the gateway runs a full app-state sync and retries up to 3 times before failing with **500**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserContactRemoveRequest"
            examples:
              by_phone:
                summary: Remove by phone number
                description: Phone number with country code, without `+`.
                value:
                  phone: "5511999999999"
              by_jid:
                summary: Remove by JID
                description: A full phone JID is accepted as well.
                value:
                  phone: 5511999999999@s.whatsapp.net
      responses:
        "200":
          description: Contact removed and synced.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserContactRemoveResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Contact removed successfully
                  phone: "5511999999999"
                success: true
        "400":
          description: "Bad Request: invalid JSON, `phone` empty, or `phone` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                empty_phone:
                  summary: phone missing or empty
                  value:
                    code: 400
                    error: phone number cannot be empty
                    success: false
                invalid_phone:
                  summary: phone cannot be parsed
                  value:
                    code: 400
                    error: could not parse phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the app-state update failed after the recovery attempts."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: App-state update failed
                  value:
                    code: 500
                    error: "failed to remove contact: app state error (not recoverable): websocket not connected"
                    success: false
  /user/status:
    get:
      tags:
        - User
      summary: Get your status (about) text
      description: |
        Returns the status ("about") text of the connected account, as WhatsApp currently reports it.

        **Rules:**
        - No query parameters or body. The session must be paired; before pairing the call returns **500** `could not get own JID`.
        - `status` is an empty string when no text is set.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Current status text.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserStatusGetResponse"
              example:
                code: 200
                data:
                  status: Available for support 9am-6pm
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, session not paired, or the WhatsApp query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_paired:
                  summary: Session has no own JID yet
                  value:
                    code: 500
                    error: could not get own JID
                    success: false
                query_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "failed to get status: websocket not connected"
                    success: false
    post:
      tags:
        - User
      summary: Set your status (about) text
      description: |
        Sets the status ("about") text of the connected account, shown on your profile to people allowed by your status privacy.

        **Rules:**
        - `status` is sent as given; an empty string or a missing field clears the text.
        - The gateway does not check the length; WhatsApp limits the text to 139 characters and rejects longer values with **500**.
        - Emoji and other multibyte text are sent as given (validated live with `💻 Setup Automatizado`).
        - The text travels in the classic `<iq type="set" xmlns="status">` stanza, the same one the official web client uses for
          the About field. The newer GraphQL "text status" mutation (emoji plus ephemeral duration) is gated per account and
          answers `400 Bad Request` outside the rollout, so it is never used for a plain text status.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetMyStatusRequest"
            examples:
              business_hours:
                summary: Set a status text
                description: Replaces the current about text.
                value:
                  status: Available for support 9am-6pm
              clear:
                summary: Clear the status text
                description: An empty string removes the about text.
                value:
                  status: ""
      responses:
        "200":
          description: Status text updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserStatusSetResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Status updated successfully
                  status: Available for support 9am-6pm
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp rejected the status text."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                set_failed:
                  summary: WhatsApp rejected the update
                  value:
                    code: 500
                    error: "failed to set status: websocket not connected"
                    success: false
  /user/update:
    post:
      tags:
        - User
      summary: Update your gateway user name or token
      description: |
        Changes the gateway user record of the caller: its display name, its API token, or both. This does not touch the WhatsApp profile (see `POST /user/pushname` for that).

        **Rules:**
        - Send `name`, `token` or both. Empty strings and values equal to the current ones are ignored; if nothing changes the request returns **400**.
        - `name` may have at most 255 characters. A new `token` needs at least 8 characters and must not belong to another user (**409**).
        - The update uses optimistic locking on the current token; a concurrent change returns **409**.

        **Side effects:**
        - When the token changes, the old token stops authenticating immediately and the cached user configuration is reloaded under the new token.
        - The response echoes the final token in clear text; treat it as a secret.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserUpdateRequest"
            examples:
              rename:
                summary: Change only the name
                description: The token stays the same.
                value:
                  name: sales-team
              rotate_token:
                summary: Rotate the API token
                description: The old token is revoked as soon as the response is sent.
                value:
                  token: 9f8e7d6c5b4a3210fedcba98
              both:
                summary: Change name and token
                description: Both values are written in the same update.
                value:
                  name: support-team
                  token: 0a1b2c3d4e5f67890a1b2c3d
      responses:
        "200":
          description: User record updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserUpdateResponse"
              example:
                code: 200
                data:
                  id: 3f0b9f2c8e7d4a6b9c1d2e3f4a5b6c7d
                  name: sales-team
                  token: 9f8e7d6c5b4a3210fedcba98
                  message: Profile updated successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, no effective change, name too long, or token shorter than 8 characters."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: Invalid JSON
                    success: false
                no_changes:
                  summary: Both fields empty or equal to the current values
                  value:
                    code: 400
                    error: No changes detected
                    success: false
                name_too_long:
                  summary: name longer than 255 characters
                  value:
                    code: 400
                    error: Name too long (max 255 characters)
                    success: false
                token_too_short:
                  summary: New token shorter than 8 characters
                  value:
                    code: 400
                    error: Token too short (min 8 characters)
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the new token belongs to another user, or the record changed during the update."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                token_in_use:
                  summary: Token already used by another user
                  value:
                    code: 409
                    error: Token already in use
                    success: false
                concurrent_update:
                  summary: Record modified by another request
                  value:
                    code: 409
                    error: Profile was modified by another request. Please try again
                    success: false
        "500":
          description: "Internal Server Error: database failure while checking or writing the record."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "Failed to update profile: pq: could not serialize access due to concurrent update"
                success: false
  /user/business/profile:
    post:
      tags:
        - User
      summary: Get a business profile
      description: |
        Returns the public WhatsApp Business profile of an account: address, email, categories, business hours and profile options, together with its status text and verified name.

        **Rules:**
        - `phone` is required: a phone number with country code or a user JID.
        - A personal account (no verified name) whose profile lookup fails with "not found" returns **404**; other lookup failures return **500**.
        - A verified business whose profile data cannot be read still returns **200** with `is_business_account: true` and `profile_data_available: false`.

        **Response:**
        - `open_time`/`close_time` are formatted as `HH:MM`; `categories`, `business_hours` and `profile_options` are `null` when the profile has none.
        - `verified_name`, `status` and `picture_id` are omitted when empty; `business_name` is never filled by this endpoint.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BusinessProfileRequest"
            examples:
              by_phone:
                summary: Business phone number
                description: Phone number with country code, without `+`.
                value:
                  phone: "5511988888888"
              by_jid:
                summary: Business JID
                description: A full phone JID is accepted as well.
                value:
                  phone: 5511988888888@s.whatsapp.net
      responses:
        "200":
          description: Business profile data.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserBusinessProfileResponse"
              examples:
                full_profile:
                  summary: Business with public profile data
                  value:
                    code: 200
                    data:
                      jid: 5511988888888@s.whatsapp.net
                      email: contact@example.com
                      address: 100 Example Avenue, Sao Paulo
                      categories:
                        - id: "133436743388217"
                          name: Shopping & Retail
                      profile_options:
                        commerce_experience: catalog
                      business_hours_timezone: America/Sao_Paulo
                      business_hours:
                        - day_of_week: mon
                          mode: specific_hours
                          open_time: "09:00"
                          close_time: "18:00"
                        - day_of_week: sat
                          mode: open_24h
                          open_time: ""
                          close_time: ""
                      verified_name: Example Store
                      status: Open Monday to Friday
                      picture_id: "1728656243"
                      is_business_account: true
                      profile_data_available: true
                    success: true
                verified_without_profile:
                  summary: Verified business without readable profile data
                  value:
                    code: 200
                    data:
                      jid: 5511988888888@s.whatsapp.net
                      email: ""
                      address: ""
                      categories: null
                      profile_options: null
                      business_hours_timezone: ""
                      business_hours: null
                      verified_name: Example Store
                      is_business_account: true
                      profile_data_available: false
                    success: true
        "400":
          description: "Bad Request: invalid JSON, `phone` missing, or `phone` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone:
                  summary: phone missing
                  value:
                    code: 400
                    error: missing phone number in payload
                    success: false
                invalid_phone:
                  summary: phone cannot be parsed
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: the account is not a WhatsApp Business account or has no public business profile."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: this number is not a WhatsApp Business account or does not have a public business profile
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the business profile query failed for another reason."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "failed to get business profile: info query timed out"
                    success: false
  /user/check:
    post:
      tags:
        - User
      summary: Check if phone numbers are on WhatsApp
      description: |
        Checks whether phone numbers have a WhatsApp account and returns, for each one, the phone JID, the LID, business verification data, the username and the default disappearing-message setting.

        **Rules:**
        - `Phone` is required and must be a non-empty array of strings (a plain string fails to decode and returns **400**). The field name is matched case-insensitively, so `phone` also works.
        - Send numbers in international format; the gateway passes them to WhatsApp as given.
        - A missing WhatsApp session returns **500** `no session`; a failed WhatsApp query returns **500**.

        **Response:**
        - `data.Users` has one entry per number, in the order returned by WhatsApp.
        - `JID` stays phone-shaped (`@s.whatsapp.net`) for backward compatibility; the LID is reported separately in `LID`.
        - `Username`, `VerifiedLevel` and `DisappearingMode` are omitted when WhatsApp does not report them. Usernames are still rolling out, so `Username` is usually absent.

        **Use Cases:**
        - Clean a contact list before a campaign
        - Discover the LID of a number before addressing it by LID
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Checkuser"
            examples:
              single:
                summary: One number
                description: Checks a single number; the array form is mandatory even for one value.
                value:
                  Phone:
                    - "5511999999999"
              several:
                summary: Several numbers
                description: Checks three numbers in one WhatsApp query.
                value:
                  Phone:
                    - "5511999999999"
                    - "5511988888888"
                    - "5511977777777"
      responses:
        "200":
          description: One result per queried number.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserCheckResponse"
              example:
                code: 200
                data:
                  Users:
                    - Query: "5511988888888"
                      IsInWhatsapp: true
                      JID: 5511988888888@s.whatsapp.net
                      LID: 123456789012346@lid
                      VerifiedName: Example Store
                      Username: examplestore
                      VerifiedLevel: high
                      DisappearingMode:
                        enabled: true
                        duration_sec: 604800
                        set_at: "2026-07-01T12:34:56Z"
                    - Query: "5511977777777"
                      IsInWhatsapp: false
                      JID: 5511977777777@s.whatsapp.net
                      LID: ""
                      VerifiedName: ""
                success: true
        "400":
          description: "Bad Request: the body cannot be decoded (for example `Phone` sent as a string) or the list is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Invalid JSON or Phone is not an array
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone:
                  summary: Phone missing or empty
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the WhatsApp registration query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: "failed to check if users are on WhatsApp: websocket not connected"
                    success: false
  /user/presence:
    post:
      tags:
        - User
      summary: Set your global presence
      description: |
        Sets the global presence of the connected account to `available` (online) or `unavailable` (offline). Contacts allowed by your "online" privacy setting see the change.

        **Rules:**
        - `type` is required and must be exactly `available` or `unavailable` (lower case).
        - WhatsApp requires the account push name to be known; right after pairing, before the push name syncs, sending presence fails with **500**.
        - Staying `available` keeps delivery receipts active and can stop push notifications on the phone. See `POST /session/presence/config` to switch back to `unavailable` automatically after connecting.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserPresence"
            examples:
              available:
                summary: Appear online
                description: Marks the account as online until changed or disconnected.
                value:
                  type: available
              unavailable:
                summary: Appear offline
                description: Marks the account as offline; the phone keeps receiving push notifications.
                value:
                  type: unavailable
      responses:
        "200":
          description: Presence sent to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserPresenceResponse"
              example:
                code: 200
                data:
                  Details: Presence set successfuly
                success: true
        "400":
          description: "Bad Request: invalid JSON, or `type` is not `available`/`unavailable`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                invalid_type:
                  summary: type missing or not allowed
                  value:
                    code: 400
                    error: "invalid presence type. Allowed values: 'available', 'unavailable'"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp did not accept the presence (for example push name not synced yet)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Presence not accepted
                  value:
                    code: 500
                    error: failure sending presence to Whatsapp servers
                    success: false
  /user/avatar:
    post:
      tags:
        - User
      summary: Get profile picture information
      description: |
        Returns the profile picture URL and metadata of a contact, group or community, either the full image or the small preview thumbnail. The image itself is not downloaded by the gateway.

        **Features:**
        - `Preview: true` returns the thumbnail; `false` returns the full-resolution image.
        - `CommonGID` fetches a member's picture through a group you share, which can succeed when the contact restricts the picture to contacts.
        - `InviteCode` fetches a group picture before joining, using the invite code.
        - `PersonaID` fetches the picture of a Meta AI persona; `IsCommunity` marks a community JID.

        **Rules:**
        - `Phone` is required: phone number, user JID, group JID or community JID. Field names are matched case-insensitively.
        - `CommonGID`, when present, must be a valid JID (**400** otherwise).
        - WhatsApp errors, including "no profile picture" and "hidden by privacy", return **500** with the reason in the message.
        - The returned `url` expires; download it promptly.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Checkavatar"
            examples:
              preview:
                summary: Thumbnail of a contact
                description: Smallest payload; good for chat lists.
                value:
                  Phone: "5511999999999"
                  Preview: true
              full_via_group:
                summary: Full picture through a shared group
                description: Uses a group both accounts belong to as the access context.
                value:
                  Phone: 5511999999999@s.whatsapp.net
                  Preview: false
                  CommonGID: 120363000000000011@g.us
              group_invite:
                summary: Group picture before joining
                description: Reads the picture of a group you are not in, authorized by its invite code.
                value:
                  Phone: 120363000000000011@g.us
                  Preview: true
                  InviteCode: AbCdEfGhIjKlMnOpQrStUv
              community:
                summary: Community picture
                description: Full picture of a community parent group.
                value:
                  Phone: 120363000000000022@g.us
                  Preview: false
                  IsCommunity: true
      responses:
        "200":
          description: Profile picture metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserAvatarResponse"
              example:
                code: 200
                data:
                  url: https://pps.whatsapp.net/v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                  id: "1700000000"
                  type: preview
                  direct_path: /v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                  hash: null
                success: true
        "400":
          description: "Bad Request: invalid JSON, `Phone` missing, or `Phone`/`CommonGID` is not a valid JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone:
                  summary: Phone missing
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                invalid_phone:
                  summary: Phone is not a valid number or JID
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                invalid_common_gid:
                  summary: CommonGID is not a valid JID
                  value:
                    code: 400
                    error: could not parse CommonGID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, no picture set, picture hidden by privacy, or another WhatsApp failure."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_set:
                  summary: The account has no profile picture
                  value:
                    code: 500
                    error: "failed to get avatar: that user or group does not have a profile picture"
                    success: false
                hidden:
                  summary: Picture hidden by the contact's privacy settings
                  value:
                    code: 500
                    error: "failed to get avatar: the user has hidden their profile picture from you"
                    success: false
                no_avatar_found:
                  summary: WhatsApp returned no picture information
                  value:
                    code: 500
                    error: no avatar found
                    success: false
  /user/photo:
    post:
      tags:
        - User
      summary: Set your own profile photo
      description: |
        Sets or replaces the profile photo of the connected WhatsApp account. The change is visible to everyone allowed by your profile photo privacy setting.

        **Rules:**
        - `Image` is required: a base64 data URL (`data:image/jpeg;base64,...`) or an `http(s)` URL that the gateway downloads.
        - The decoded bytes must be a JPEG image (checked by its file signature); PNG, WebP and other formats return **400**.
        - Square images up to 640x640 px are recommended; WhatsApp may reject very large images.

        **Response quirk:**
        - When WhatsApp rejects the upload, the response has HTTP **500** but the legacy body `success: true` with the error text in `data` (no `error` field). Check the HTTP status, not `success`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetProfilePhoto"
            examples:
              base64:
                summary: JPEG as a base64 data URL
                description: The data URL must carry JPEG bytes; the media type in the prefix is not checked, the bytes are.
                value:
                  Image: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AKp//2Q==
              url:
                summary: JPEG downloaded from a URL
                description: The gateway downloads the file and validates the JPEG signature before uploading it.
                value:
                  Image: https://example.com/images/profile.jpg
      responses:
        "200":
          description: Profile photo updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserPhotoResponse"
              example:
                code: 200
                data:
                  Details: Profile photo set successfully
                  PictureID: "1757862000"
                success: true
        "400":
          description: "Bad Request: invalid JSON, `Image` missing or not a data URL/URL, download failure, empty data, or non-JPEG image."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_image:
                  summary: Image missing
                  value:
                    code: 400
                    error: missing Image in Payload
                    success: false
                unsupported_input:
                  summary: Image is neither a data URL nor an HTTP(S) URL
                  value:
                    code: 400
                    error: Image must be a base64 data URL ("data:image/jpeg;base64,...") or an HTTP/HTTPS URL
                    success: false
                download_failed:
                  summary: The URL could not be downloaded
                  value:
                    code: 400
                    error: "could not download image from URL: HTTP error 404 when downloading from URL"
                    success: false
                bad_base64:
                  summary: Invalid base64 in the data URL
                  value:
                    code: 400
                    error: could not decode base64 encoded data from payload
                    success: false
                empty_image:
                  summary: Data URL without bytes
                  value:
                    code: 400
                    error: no image data found in payload
                    success: false
                not_jpeg:
                  summary: Image is not JPEG
                  value:
                    code: 400
                    error: image must be in JPEG format. WhatsApp only accepts JPEG images for profile photos
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (standard error body), or WhatsApp rejected the photo (legacy body with `success: true` and the message in `data`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_rejected_legacy:
                  summary: WhatsApp rejected the photo (legacy body)
                  value:
                    code: 500
                    data: "failed to set profile photo: websocket not connected"
                    success: true
  /user/photo/remove:
    post:
      tags:
        - User
      summary: Remove your own profile photo
      description: |
        Removes the profile photo of the connected WhatsApp account; contacts then see the default placeholder avatar.

        **Rules:**
        - No request body is read.
        - Removing when no photo is set is forwarded to WhatsApp as is.

        **Response quirk:**
        - When WhatsApp rejects the removal, the response has HTTP **500** but the legacy body `success: true` with the error text in `data` (no `error` field). Check the HTTP status, not `success`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Profile photo removed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserPhotoRemoveResponse"
              example:
                code: 200
                data:
                  Details: Profile photo removed successfully
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (standard error body), or WhatsApp rejected the removal (legacy body with `success: true` and the message in `data`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                removal_rejected_legacy:
                  summary: WhatsApp rejected the removal (legacy body)
                  value:
                    code: 500
                    data: "failed to remove profile photo: websocket not connected"
                    success: true
  /user/contacts:
    get:
      tags:
        - User
      summary: List saved contacts
      description: |
        Lists every contact kept in the local contact store of the session: saved address-book names, push names, business names and usernames learned from app state and messages.

        **Rules:**
        - No parameters. Data comes from the local store; WhatsApp is not queried.
        - The session must be paired (**500** `not logged in`); an empty store returns **200** with an empty object.

        **Response:**
        - `data` is an object keyed by JID (phone JIDs and LIDs can both appear).
        - `RedactedPhone` is only filled for LID members seen in groups, in the masked form WhatsApp shows (for example `+55∙∙∙∙∙∙∙∙99`).
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Contacts keyed by JID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserContactsResponse"
              example:
                code: 200
                data:
                  5511999999999@s.whatsapp.net:
                    Found: true
                    FirstName: Maria
                    FullName: Maria Silva
                    PushName: Maria
                    BusinessName: ""
                    Username: ""
                    RedactedPhone: ""
                  123456789012346@lid:
                    Found: true
                    FirstName: ""
                    FullName: ""
                    PushName: Example Store
                    BusinessName: Example Store
                    Username: examplestore
                    RedactedPhone: "+55∙∙∙∙∙∙∙∙88"
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, session not paired, or the contact store failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_logged_in:
                  summary: Session not paired yet
                  value:
                    code: 500
                    error: not logged in
                    success: false
  /user/lid/get:
    get:
      tags:
        - User LID
      summary: Look up the LID of a phone number (query string)
      description: |
        Looks up, in the local LID store of this session, the LID mapped to a phone number or phone JID. Use this GET form when the number fits in the query string.

        **Rules:**
        - Pass the number in the `phone` query parameter. Without it the handler tries to read a JSON body, and a GET without body fails with `Invalid JSON format`.
        - `phone` accepts a number with country code (a leading `+` is stripped) or a phone JID. A value that is already a LID returns **400**.
        - Only the local store is consulted; WhatsApp is not queried. An unknown number returns **200** with `has_lid: false`.

        **Response quirk:**
        - Validation and store errors of this endpoint use a legacy body: the HTTP status is 400/500 but the body has `success: true` and the message in `data.error`. Check the HTTP status.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: phone
          in: query
          required: false
          description: Phone number with country code, or phone JID. Required unless a JSON body with `phone` is sent.
          schema:
            type: string
          example: "5511999999999"
      responses:
        "200":
          description: Lookup result; `has_lid` tells whether a mapping exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserLIDGetResponse"
              examples:
                mapped:
                  summary: Mapping found
                  value:
                    code: 200
                    data:
                      success: true
                      phone: 5511999999999@s.whatsapp.net
                      lid: 123456789012345@lid
                      has_lid: true
                    success: true
                not_mapped:
                  summary: No mapping stored yet
                  value:
                    code: 200
                    data:
                      success: true
                      phone: 5511977777777@s.whatsapp.net
                      lid: ""
                      has_lid: false
                    success: true
        "400":
          description: "Bad Request (legacy body): `phone` missing, unparseable, or already a LID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_phone_no_body:
                  summary: No phone query parameter and no JSON body
                  value:
                    code: 400
                    data:
                      error: Invalid JSON format
                    success: true
                phone_required:
                  summary: Body sent without phone
                  value:
                    code: 400
                    data:
                      error: Phone parameter is required
                    success: true
                unparseable:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    data:
                      error: Could not parse phone number or JID
                    success: true
                already_lid:
                  summary: phone is already a LID
                  value:
                    code: 400
                    data:
                      error: Provided identifier is already a LID, not a phone number
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error (legacy body): no WhatsApp session, or the LID store query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_client:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    data:
                      error: WhatsApp client not found
                    success: true
                store_failed:
                  summary: LID store query failed
                  value:
                    code: 500
                    data:
                      error: "Failed to get LID: sql: database is closed"
                    success: true
    post:
      tags:
        - User LID
      summary: Look up the LID of a phone number (JSON body)
      description: |
        Looks up, in the local LID store of this session, the LID mapped to a phone number or phone JID, taking the number from a JSON body. Behaves like the GET form; use it when your client prefers POST bodies.

        **Rules:**
        - Send `phone` in the body. If the `phone` query parameter is also present, the query value wins and the body is not read.
        - `phone` accepts a number with country code (a leading `+` is stripped) or a phone JID. A value that is already a LID returns **400**.
        - Only the local store is consulted; an unknown number returns **200** with `has_lid: false`.

        **Response quirk:**
        - Validation and store errors use a legacy body with `success: true` and the message in `data.error`, even though the HTTP status is 400/500.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: phone
          in: query
          required: false
          description: Optional override. When present, it is used instead of the body `phone`.
          schema:
            type: string
          example: "5511999999999"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GetLIDRequest"
            examples:
              number:
                summary: Phone number
                description: Number with country code, without `+`.
                value:
                  phone: "5511999999999"
              jid:
                summary: Phone JID
                description: A full phone JID is accepted as well.
                value:
                  phone: 5511999999999@s.whatsapp.net
      responses:
        "200":
          description: Lookup result; `has_lid` tells whether a mapping exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserLIDGetResponse"
              example:
                code: 200
                data:
                  success: true
                  phone: 5511999999999@s.whatsapp.net
                  lid: 123456789012345@lid
                  has_lid: true
                success: true
        "400":
          description: "Bad Request (legacy body): invalid JSON, `phone` missing, unparseable, or already a LID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    data:
                      error: Invalid JSON format
                    success: true
                phone_required:
                  summary: phone missing or empty
                  value:
                    code: 400
                    data:
                      error: Phone parameter is required
                    success: true
                unparseable:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    data:
                      error: Could not parse phone number or JID
                    success: true
                already_lid:
                  summary: phone is already a LID
                  value:
                    code: 400
                    data:
                      error: Provided identifier is already a LID, not a phone number
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error (legacy body): no WhatsApp session, or the LID store query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_client:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    data:
                      error: WhatsApp client not found
                    success: true
                store_failed:
                  summary: LID store query failed
                  value:
                    code: 500
                    data:
                      error: "Failed to get LID: sql: database is closed"
                    success: true
  /user/lid/from-lid:
    get:
      tags:
        - User LID
      summary: Look up the phone number of a LID (query string)
      description: |
        Translates a LID back to the phone JID using the local LID store of this session. This GET form takes the LID from the query string.

        **Rules:**
        - Pass the LID in the `lid` query parameter, including the `@lid` suffix. Without it the handler tries to read a JSON body, and a GET without body fails with `Invalid JSON format`.
        - A value that is not an `@lid` JID returns **400**.
        - Only the local store is consulted; a LID without a stored mapping returns **404**.

        **Response quirk:**
        - Every non-401 error uses a legacy body with `success: true` and the message in `data.error`. Check the HTTP status.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: lid
          in: query
          required: false
          description: LID with `@lid` suffix. Required unless a JSON body with `lid` is sent.
          schema:
            type: string
          example: 123456789012345@lid
      responses:
        "200":
          description: Phone JID mapped to the LID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserJIDFromLIDResponse"
              example:
                code: 200
                data:
                  success: true
                  lid: 123456789012345@lid
                  phone: 5511999999999@s.whatsapp.net
                success: true
        "400":
          description: "Bad Request (legacy body): `lid` missing, malformed, or not an `@lid` JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_lid_no_body:
                  summary: No lid query parameter and no JSON body
                  value:
                    code: 400
                    data:
                      error: Invalid JSON format
                    success: true
                lid_required:
                  summary: Body sent without lid
                  value:
                    code: 400
                    data:
                      error: LID parameter is required
                    success: true
                malformed:
                  summary: lid cannot be parsed as a JID
                  value:
                    code: 400
                    data:
                      error: "Invalid LID format: unexpected number of dots in JID"
                    success: true
                not_lid:
                  summary: lid is a phone JID
                  value:
                    code: 400
                    data:
                      error: Provided identifier is not a LID
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found (legacy body): the LID has no phone mapping in the local store."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 404
                data:
                  error: Phone number not found for this LID
                success: true
        "500":
          description: "Internal Server Error (legacy body): no WhatsApp session, or the LID store query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_client:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    data:
                      error: WhatsApp client not found
                    success: true
                store_failed:
                  summary: LID store query failed
                  value:
                    code: 500
                    data:
                      error: "Failed to get phone number: sql: database is closed"
                    success: true
    post:
      tags:
        - User LID
      summary: Look up the phone number of a LID (JSON body)
      description: |
        Translates a LID back to the phone JID using the local LID store of this session, taking the LID from a JSON body. Same result as the GET form.

        **Rules:**
        - Send `lid` in the body with the `@lid` suffix. If the `lid` query parameter is also present, the query value wins and the body is not read.
        - A value that is not an `@lid` JID returns **400**; a LID without a stored mapping returns **404**.

        **Response quirk:**
        - Every non-401 error uses a legacy body with `success: true` and the message in `data.error`, even though the HTTP status is 400/404/500.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: lid
          in: query
          required: false
          description: Optional override. When present, it is used instead of the body `lid`.
          schema:
            type: string
          example: 123456789012345@lid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GetJIDFromLIDRequest"
            examples:
              lid:
                summary: LID from a group participant
                description: LIDs usually come from group events, where members are addressed by LID.
                value:
                  lid: 123456789012345@lid
      responses:
        "200":
          description: Phone JID mapped to the LID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserJIDFromLIDResponse"
              example:
                code: 200
                data:
                  success: true
                  lid: 123456789012345@lid
                  phone: 5511999999999@s.whatsapp.net
                success: true
        "400":
          description: "Bad Request (legacy body): invalid JSON, `lid` missing, malformed, or not an `@lid` JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    data:
                      error: Invalid JSON format
                    success: true
                lid_required:
                  summary: lid missing or empty
                  value:
                    code: 400
                    data:
                      error: LID parameter is required
                    success: true
                not_lid:
                  summary: lid is a phone JID
                  value:
                    code: 400
                    data:
                      error: Provided identifier is not a LID
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found (legacy body): the LID has no phone mapping in the local store."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 404
                data:
                  error: Phone number not found for this LID
                success: true
        "500":
          description: "Internal Server Error (legacy body): no WhatsApp session, or the LID store query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_client:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    data:
                      error: WhatsApp client not found
                    success: true
                store_failed:
                  summary: LID store query failed
                  value:
                    code: 500
                    data:
                      error: "Failed to get phone number: sql: database is closed"
                    success: true
  /user/lid/mappings:
    get:
      tags:
        - User LID
      summary: List LID mappings of saved contacts
      description: |
        Lists the phone-to-LID mappings known for the contacts in the local contact store. For each stored contact the gateway looks up its LID and returns the pairs that exist.

        **Rules:**
        - No parameters; WhatsApp is not queried.
        - Only contacts present in the contact store are considered, so this is not the complete LID table of the session.
        - The session must be paired (**500** `not logged in`).

        **Response:**
        - `mappings` is `null` and `message` is `No LID mappings found` when no pair exists.
        - A missing client and contact-store failures use a legacy error body (`success: true`, message in `data.error`).
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored phone/LID pairs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LIDMappingsResponse"
              examples:
                with_mappings:
                  summary: Mappings found
                  value:
                    code: 200
                    data:
                      success: true
                      mappings:
                        - lid: 123456789012345@lid
                          phone: 5511999999999@s.whatsapp.net
                        - lid: 123456789012346@lid
                          phone: 5511988888888@s.whatsapp.net
                      count: 2
                    success: true
                empty:
                  summary: No mappings yet
                  value:
                    code: 200
                    data:
                      success: true
                      mappings: null
                      count: 0
                      message: No LID mappings found
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: session not paired (standard body), or no client / contact store failure (legacy body)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                not_logged_in:
                  summary: Session not paired yet
                  value:
                    code: 500
                    error: not logged in
                    success: false
                no_client_legacy:
                  summary: No WhatsApp session for this user (legacy body)
                  value:
                    code: 500
                    data:
                      error: WhatsApp client not found
                    success: true
                store_failed_legacy:
                  summary: Contact store failure (legacy body)
                  value:
                    code: 500
                    data:
                      error: "Failed to get contacts: sql: database is closed"
                    success: true
  /chat/delete:
    post:
      tags:
        - Chat
      summary: Delete a message for everyone
      description: |
        Revokes a message so every participant of the chat sees "This message was deleted" (delete for everyone).

        **Rules:**
        - `Phone` and `Id` are required. `Phone` is the chat: a phone number, a user JID or a group JID.
        - **Own message** (any chat): send only `Phone` and `Id`.
        - **Group admin revoke:** to delete another participant's message in a group, add `Sender` with the original author's JID (`@s.whatsapp.net` or `@lid`). WhatsApp rejects it when this account is not a group admin.
        - WhatsApp enforces its own time window for deleting messages; late revokes fail with **500**.
        - `data.Id` echoes the revoked message ID, not the ID of the revoke protocol message.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDeleteMessageRequest"
            examples:
              own_message:
                summary: Revoke your own message
                description: Deletes a message this account sent in a direct chat.
                value:
                  Phone: "5511999999999"
                  Id: 3EB0C767D26A1D6B8A9F
              group_admin_revoke:
                summary: Group admin revokes another member's message
                description: "`Sender` is the original author. WhatsApp rejects it when the session is not a group admin."
                value:
                  Phone: "120363000000000011@g.us"
                  Id: 3EB0C767D26A1D6B8A9E
                  Sender: 123456789012345@lid
      responses:
        "200":
          description: The revoke was sent and acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDeleteMessageResponse"
              example:
                code: 200
                data:
                  Details: Deleted
                  Id: 3EB0C767D26A1D6B8A9F
                  Timestamp: "2026-09-15T10:30:00-03:00"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `Phone` or `Id` is missing, or `Phone`/`Sender` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone:
                  summary: Phone missing
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_id:
                  summary: Id missing
                  value:
                    code: 400
                    error: missing Id in Payload
                    success: false
                invalid_phone:
                  summary: Phone cannot be parsed
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                invalid_sender:
                  summary: Sender cannot be parsed
                  value:
                    code: 400
                    error: could not parse Sender
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the revoke."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Revoke rejected by the server
                  value:
                    code: 500
                    error: "error sending message: server returned error 403"
                    success: false
  /chat/markread:
    post:
      tags:
        - Chat
      summary: Mark messages as read
      description: |
        Sends a read receipt for one or more messages of a chat, clearing the unread badge on the linked devices and, when privacy allows, showing blue ticks to the sender.

        **Rules:**
        - `Id` (array, at least one) and `Chat` are required.
        - `Chat` must be the full chat JID as received in the webhook (`event.Info.Chat`), for example `5511999999999@s.whatsapp.net`, `123456789012345@lid` or `120363000000000011@g.us`. A bare phone number is **not** converted to a JID here; a malformed JID returns **400** `could not decode Payload`.
        - `Sender` (`event.Info.Sender`) is required in groups, where it is sent as the receipt participant; it is ignored for direct chats.
        - All IDs in one call must come from the same sender; call once per sender otherwise.

        **Blue ticks:**
        - When this account's `read_receipts` privacy setting is `none`, and always for channels (`@newsletter`), a `read-self` receipt is sent instead: the chat is marked as read on your own devices but no blue ticks are shown. The call still returns 200.
        - A 200 means the receipt was handed to WhatsApp; there is no delivery confirmation.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatMarkReadRequest"
            examples:
              direct_chat:
                summary: Mark two messages of a direct chat as read
                description: "`Sender` is not needed for direct chats."
                value:
                  Id:
                    - 3EB0C767D26A1D6B8A9F
                    - 3EB0A1B2C3D4E5F60718
                  Chat: 5511999999999@s.whatsapp.net
              group_chat:
                summary: Mark a group message as read
                description: In groups `Sender` is required and identifies the author of the messages.
                value:
                  Id:
                    - 3EB0C767D26A1D6B8A9F
                  Chat: 120363000000000011@g.us
                  Sender: 123456789012345@lid
      responses:
        "200":
          description: The read receipt was sent to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatMarkReadResponse"
              example:
                code: 200
                data:
                  Details: Message(s) marked as read
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON (or a JID is malformed), `Chat` is missing, or `Id` is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON or a JID is malformed
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_chat:
                  summary: Chat missing
                  value:
                    code: 400
                    error: missing Chat in Payload
                    success: false
                missing_id:
                  summary: Id empty or missing
                  value:
                    code: 400
                    error: missing Id in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the receipt could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Receipt could not be sent
                  value:
                    code: 500
                    error: failure marking messages as read
                    success: false
  /chat/react:
    post:
      tags:
        - Chat
      summary: React to a message
      description: |
        Sends an emoji reaction to a message, or removes this account's reaction. The reaction is a new message with its own ID that targets the original message key.

        **Addressing (iOS and LID):**
        - iOS drops a reaction silently when its target key is not in the same namespace (phone number vs LID) as the stored original message. Android and Web are lenient.
        - Send back what the webhook gave you: `Chat` with `event.Info.Chat` and, in groups, `Sender` with `event.Info.Sender`.
        - When `Chat` is given it is used as both the send destination and the key chat; otherwise `Phone` is used and the library's LID migration realigns the key.

        **Rules:**
        - `Phone` and `Id` are required. `Body` is the emoji; an empty `Body` or `"remove"` removes the reaction.
        - Prefix `Id` with `me:` when the target message was sent by this account.
        - `Sender` is only used for another participant's message in a group and must be a full JID; an unparsable value is silently ignored.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatReactRequest"
            examples:
              private_received:
                summary: React to a received message in a direct chat
                description: The phone number is enough for direct chats.
                value:
                  Phone: "5511999999999"
                  Body: "🔥"
                  Id: 2AFA4C0C20D815D8055A
              private_own:
                summary: React to your own message
                description: The `me:` prefix marks the target message as sent by this account.
                value:
                  Phone: "5511999999999"
                  Body: "👍"
                  Id: me:3EB07B61F567BC21530C37
              private_explicit_lid:
                summary: Explicit LID addressing via Chat
                description: "`Chat` with the webhook `@lid` chat guarantees rendering on iOS."
                value:
                  Phone: "5511999999999"
                  Chat: 123456789012345@lid
                  Body: "👏"
                  Id: 2AFA4C0C20D815D8055A
              group_other_member:
                summary: React to another member's group message
                description: "`Sender` carries the author from the webhook (`event.Info.Sender`)."
                value:
                  Phone: "120363000000000011@g.us"
                  Body: "❤️"
                  Id: 2A565EE7389E4B787A9B
                  Sender: 123456789012345@lid
              remove:
                summary: Remove a reaction
                description: An empty `Body` (or `"remove"`) clears this account's reaction.
                value:
                  Phone: "5511999999999"
                  Body: remove
                  Id: 2AFA4C0C20D815D8055A
      responses:
        "200":
          description: The reaction was sent and acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatReactResponse"
              example:
                code: 200
                data:
                  Details: Sent
                  Id: 3EB0D1E2F3A4B5C6D7E8
                  Timestamp: "2026-09-15T10:30:00-03:00"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `Id` or `Phone` is missing, or `Phone`/`Chat` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_fields:
                  summary: Id or Phone missing
                  value:
                    code: 400
                    error: id and phone are required
                    success: false
                invalid_phone:
                  summary: Phone cannot be parsed
                  value:
                    code: 400
                    error: could not parse phone number
                    success: false
                invalid_chat:
                  summary: Chat cannot be parsed
                  value:
                    code: 400
                    error: could not parse chat JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the reaction could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Reaction could not be sent
                  value:
                    code: 500
                    error: "error sending reaction: websocket not connected"
                    success: false
  /chat/send/text:
    post:
      tags:
        - Send
      summary: Sends a text message
      description: |
        Sends a plain text message to a contact, group or channel.

        **Features:**
        - Optional custom `Id`; a random ID is generated otherwise
        - Typing indicator (`Presence`, in ms) before sending
        - Disappearing messages (`Duration`: 86400, 604800 or 7776000 seconds)
        - Automatic link preview (`LinkPreview: true`) and number check (`NumberCheck: true`)

        **Rules:**
        - `Phone` and `Body` are required
        - Replies: send `ContextInfo.stanzaID` (message being replied to) together with `ContextInfo.participant` (its author)
        - Add `ContextInfo.quotedMessage` with the original content for a full reply preview on every device; without it the reply works but the preview may be missing
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageText"
            examples:
              simple:
                summary: Simple text
                description: Sends a text message with a generated ID.
                value:
                  Phone: "5511999999999"
                  Body: Hello! Your order has shipped.
              reply_with_preview:
                summary: Reply with full quote preview
                description: Replies to a message and includes `quotedMessage` so every device shows the preview.
                value:
                  Phone: "5511999999999"
                  Body: This is my reply to your message
                  ContextInfo:
                    stanzaID: 3EB06F9067F80BAB89FF
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: Original message text being replied to
              typing_link_preview:
                summary: Typing indicator, link preview and disappearing timer
                description: Shows typing for 3 s, generates a link preview, checks the number and sets a 24 h timer.
                value:
                  Phone: "5511999999999"
                  Body: Track your order at https://example.com/orders/1234
                  Id: 3EB0C767D26A1D6B8A9F
                  Presence: 3000
                  Duration: 86400
                  LinkPreview: true
                  NumberCheck: true
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Body in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_body_in_payload:
                  summary: missing Body in Payload
                  value:
                    code: 400
                    error: missing Body in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "error sending message: server returned error 479"
                    success: false
  /chat/send/link:
    post:
      tags:
        - Send
      summary: Send a link message with optional preview override.
      description: |
        Sends a text message containing a link, with an automatic, customized or disabled preview card.

        **Features:**
        - Default: the preview (title, description, image) is scraped from the URL's OpenGraph tags
        - `DisablePreview: true` sends the link as plain text without a card
        - `Preview` with `fetch_missing` omitted/false: REPLACE mode, only the fields you set are used and nothing is scraped
        - `Preview` with `fetch_missing: true`: MERGE mode, the URL is scraped first and non-empty override fields win
        - Captions, mentions, replies and forwarding via `ContextInfo`

        **Custom thumbnail (`Preview.image`):**
        - Base64 data URL (≤ 5 MB) or http(s) URL (≤ 8 MB); PNG, WebP and GIF are re-encoded to JPEG (transparency flattened on white)
        - For identical cards on mobile and Web/Desktop use a 1.91:1 landscape JPEG, ideally 1200 × 630 px (at least ~400 px on the long edge)
        - The server uploads a high-res JPEG (downscaled to 800 px) and embeds a small inline JPEG (~200 px); Web paints the inline one first
        - Square images of 200 px or less are treated as favicons (smaller card)
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageLink"
            examples:
              auto-scrape:
                summary: Auto-scraped preview (default)
                description: "No `Preview` and no `DisablePreview`: the gateway scrapes the URL's OpenGraph tags. Works for public sites with valid metadata."
                value:
                  Phone: "5511999999999"
                  Link: https://github.com/tulir/whatsmeow
                  Caption: Check out this WhatsApp library
                  Id: ABCDABCD1234
              custom-preview-replace:
                summary: Custom preview (REPLACE, no scrape)
                description: "Full override: only the fields set in `Preview` are used and the URL is not fetched. Ideal for coupon links, trackers and URLs without OpenGraph."
                value:
                  Phone: "5511999999999"
                  Link: https://example.com/promo
                  Caption: Check out this deal
                  Id: ABCDABCD1234
                  Preview:
                    title: "Coupon: 50% off everything"
                    description: Use code SAVE50 at checkout
                    image: data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
                    matched_text: https://example.com/promo
                    type: image
              custom-preview-merge:
                summary: Custom preview (MERGE, scrape + override)
                description: Auto-fetch first; then the non-empty `Preview` fields overwrite the scraped result. Useful to fix only the title/image while keeping the rest.
                value:
                  Phone: "5511999999999"
                  Link: https://github.com/tulir/whatsmeow
                  Id: ABCDABCD1234
                  Preview:
                    title: 🚀 Custom title overrides the scraped one
                    fetch_missing: true
              image-url-preview:
                summary: Custom preview with an image URL
                description: Same as REPLACE, but the card image is downloaded server-side from a public URL (≤ 8 MB) instead of base64.
                value:
                  Phone: "5511999999999"
                  Link: https://example.com/article
                  Id: ABCDABCD1234
                  Preview:
                    title: Blog article
                    description: Summary of the content
                    image: https://picsum.photos/seed/zuckzapgo/600/315
                    type: image
              video-preview:
                summary: Video preview card
                description: "Video-type preview card. WhatsApp tries to play it inline unless `do_not_play_inline: true`."
                value:
                  Phone: "5511999999999"
                  Link: https://www.youtube.com/watch?v=dQw4w9WgXcQ
                  Caption: 🎬
                  Id: ABCDABCD1234
                  Preview:
                    title: Never Gonna Give You Up
                    description: Rick Astley — official video
                    image: data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
                    type: video
                    do_not_play_inline: false
              disable-preview:
                summary: No preview (plain text)
                description: Sends the link as plain text without a card. Auto-scrape is skipped; `Preview`, if sent, is ignored.
                value:
                  Phone: "5511999999999"
                  Link: https://internal.example.com/sensitive-tracker?uid=abc123
                  Caption: Internal link (no preview)
                  Id: ABCDABCD1234
                  DisablePreview: true
              reply-with-preview:
                summary: Reply with a custom preview
                description: Combines a quote (`ContextInfo.stanzaID`) with a custom preview card.
                value:
                  Phone: "5511999999999"
                  Link: https://example.com/promo
                  Id: ABCDABCD1234
                  Preview:
                    title: Reply with link
                    description: Custom reply card
                    type: placeholder
                  ContextInfo:
                    stanzaID: 3EB06F9067F80BAB89FF
                    participant: 5511999999999@s.whatsapp.net
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode payload`
            - `missing Phone in payload`
            - `missing Link in payload`
            - `invalid URL format`
            - `invalid preview.type: must be 'image', 'video', 'none', or 'placeholder'`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode payload
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in payload
                  value:
                    code: 400
                    error: missing Phone in payload
                    success: false
                missing_link_in_payload:
                  summary: missing Link in payload
                  value:
                    code: 400
                    error: missing Link in payload
                    success: false
                invalid_url_format:
                  summary: invalid URL format
                  value:
                    code: 400
                    error: invalid URL format
                    success: false
                unknown_preview_type:
                  summary: Unknown preview type
                  value:
                    code: 400
                    error: "invalid preview.type: must be 'image', 'video', 'none', or 'placeholder'"
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error sending link message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "error sending link message: server returned error 479"
                    success: false
  /chat/send/edit:
    post:
      tags:
        - Send
      summary: Edit an existing message (text or media caption)
      description: |
        Edits a message previously sent by this session, within 15 minutes of sending.

        **Features:**
        - Edits the **text** of a text message or the **caption** of an image, video or document
        - The target type is detected automatically from the message `Id`; `Body` carries the new text or caption
        - Captions can be edited on media sent through this API and on media sent from the operator's phone

        **Rules:**
        - `Phone`, the message ID (`Id`, or the `MessageID`/`message_id` alias, which takes precedence) and `Body` are required
        - When the ID is unknown to this instance the edit is sent as a text edit
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageEdit"
            examples:
              text_edit:
                summary: Edit the text of a sent message
                value:
                  Phone: "5511999999999"
                  Id: 3EB0C767D26A1D6B8A9F
                  Body: Meeting moved to 3 PM
                description: Replaces the text of a message sent by this session.
              caption_edit_message_id:
                summary: Edit an image caption using the MessageID alias
                description: "`MessageID`/`message_id` takes precedence over `Id`. The target type (text, image, video, document) is detected from the remembered message."
                value:
                  Phone: 120363000000000001@g.us
                  MessageID: 3EB0C767D26A1D6B8A9F
                  Body: Updated price list (valid until Friday)
              snake_case_link_preview:
                summary: snake_case keys with link preview on a text edit
                value:
                  phone: "5511988888888"
                  message_id: 3EB0C767D26A1D6B8A9F
                  body: "New link: https://example.com/checkout"
                  link_preview: true
                description: Edits a text message with snake_case keys and generates a link preview for the new URL.
      responses:
        "200":
          description: Edit sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26A1D6B8A9F
                      Timestamp: "2026-09-15T10:30:00-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Body in Payload`
            - `missing Id or MessageID in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_body_in_payload:
                  summary: missing Body in Payload
                  value:
                    code: 400
                    error: missing Body in Payload
                    success: false
                missing_id_or_messageid_in_payload:
                  summary: missing Id or MessageID in Payload
                  value:
                    code: 400
                    error: missing Id or MessageID in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error sending edit message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "error sending edit message: server returned error 479"
                    success: false
  /chat/send/image:
    post:
      tags:
        - Send
      summary: Sends an image/picture message
      description: |
        Sends an image to a contact, group or channel.

        **Media input (`Image`):** a base64 data URL (`data:image/png;base64,...`) or an `http://`/`https://` URL downloaded server-side (30 s timeout, must answer 200). For data URLs the type comes from the data URL header; for URLs it is detected from the content. The detected type must be `image/jpeg`, `image/png`, `image/gif` or `image/webp` (400 `invalid image format. Supported: JPEG, PNG, GIF, WebP`). The bytes must be a decodable image because a 72 px thumbnail is generated (500 otherwise). The handler enforces no size limit; WhatsApp's media limits apply.

        **Accepted keys** (either spelling): `phone`/`Phone`, `image`/`Image`, `caption`/`Caption`, `id`/`Id`, `mime_type`/`MimeType`, `presence`/`Presence`, `view_once`/`ViewOnce`, `duration`/`Duration`, `number_check`/`NumberCheck`, `context_info`/`ContextInfo`, plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone` and `Image` are required.
        - `MimeType` only overrides the `mimetype` written in the message; it does not bypass the format check.
        - `presence` (ms) shows a typing indicator and **delays the HTTP response** by that time.
        - `duration` (seconds) is snapped to the nearest of 86400, 604800 or 7776000.
        - `number_check: true` verifies the number first (400 `phone number is not registered on WhatsApp`).
        - Replies: `ContextInfo.stanzaID` and `ContextInfo.participant` must be sent together (400 `missing Participant in ContextInfo`); add `quotedMessage` for a full preview. With the PascalCase `ContextInfo` key the object is only applied when it has `stanzaID` — to mention or forward without replying use `mention_info` (`mentioned_jid`, `mention_all`) or `forward_info.is_forwarded`.
        - Invalid media input returns 400 (`invalid media input: must be either base64 data URL or HTTP(S) URL`, `could not decode base64 data: ...`, `HTTP error 404 when downloading from URL`).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageImage"
            examples:
              url_with_caption:
                summary: Image from an HTTPS URL with caption
                value:
                  Phone: "5511999999999"
                  Image: https://example.com/media/product.jpg
                  Caption: Your order is ready for pickup
                  Id: 3EB0C767D26A1D6B8A9F
                description: Downloads the image server-side and sends it with a caption and a custom message ID.
              base64_png:
                summary: Image as a short base64 data URL
                value:
                  Phone: "5511999999999"
                  Image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
                  Caption: Test pixel
                  NumberCheck: true
                description: Sends a tiny PNG embedded as a data URL after checking that the number is on WhatsApp.
              snake_case_view_once:
                summary: snake_case keys, view once, typing delay
                value:
                  phone: "5511988888888"
                  image: https://example.com/media/receipt.png
                  view_once: true
                  presence: 2000
                  number_check: true
                  EchoApi: true
                description: Uses snake_case keys, sends as view once and shows a 2 s typing indicator first.
              group_reply_with_mention:
                summary: Group reply quoting a message and mentioning a member
                value:
                  Phone: 120363000000000001@g.us
                  Image: https://example.com/media/chart.png
                  Caption: Here is the chart @5511988888888
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: Can you share the chart?
                  mention_info:
                    mentioned_jid:
                      - 5511988888888@s.whatsapp.net
                description: Replies to a group message with a full quote preview and mentions a member.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Image in Payload`
            - `invalid image format. Supported: JPEG, PNG, GIF, WebP`
            - `invalid media input: must be either base64 data URL or HTTP(S) URL`
            - `could not decode base64 data:` followed by the base64 decoding error
            - `HTTP error 404 when downloading from URL`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_image_in_payload:
                  summary: missing Image in Payload
                  value:
                    code: 400
                    error: missing Image in Payload
                    success: false
                unsupported_image_type:
                  summary: Unsupported image type
                  value:
                    code: 400
                    error: "invalid image format. Supported: JPEG, PNG, GIF, WebP"
                    success: false
                media_is_neither_data_url_nor_http_s_url:
                  summary: Media is neither data URL nor HTTP(S) URL
                  value:
                    code: 400
                    error: "invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                invalid_base64_data:
                  summary: Invalid base64 data
                  value:
                    code: 400
                    error: "could not decode base64 data: illegal base64 data at input byte 12"
                    success: false
                media_url_returned_404:
                  summary: Media URL returned 404
                  value:
                    code: 400
                    error: HTTP error 404 when downloading from URL
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to upload file:` followed by the WhatsApp media upload error
            - `could not decode image for thumbnail preparation:` followed by the image decoding error
            - `Could not create temp file for thumbnail:` followed by the file system error
            - `Failed to encode jpeg:` followed by the JPEG encoding error
            - `Failed to read` followed by the temporary thumbnail path and the read error
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "failed to upload file: failed to refresh media connections: websocket not connected"
                    success: false
                image_cannot_be_decoded:
                  summary: Image cannot be decoded
                  value:
                    code: 500
                    error: "could not decode image for thumbnail preparation: image: unknown format"
                    success: false
                thumbnail_temp_file_failed:
                  summary: Thumbnail temp file failed
                  value:
                    code: 500
                    error: "Could not create temp file for thumbnail: open /tmp/resized-2718281828.jpg: no space left on device"
                    success: false
                thumbnail_encoding_failed:
                  summary: Thumbnail encoding failed
                  value:
                    code: 500
                    error: "Failed to encode jpeg: write /tmp/resized-2718281828.jpg: no space left on device"
                    success: false
                thumbnail_read_failed:
                  summary: Thumbnail read failed
                  value:
                    code: 500
                    error: "Failed to read /tmp/resized-2718281828.jpg: unexpected EOF"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false
  /chat/send/audio:
    post:
      tags:
        - Send
      summary: Sends an audio message
      description: |
        Sends an audio file or a voice note (PTT) to a contact, group or channel.

        **Media input (`Audio`):** a base64 data URL (`data:audio/ogg;base64,...`) or an `http://`/`https://` URL downloaded server-side (30 s timeout). Any audio type is accepted; OGG/Opus gives the best voice-note playback.

        **Accepted keys** (either spelling): `phone`/`Phone`, `audio`/`Audio`, `caption`/`Caption`, `id`/`Id`, `ptt`/`PTT`, `presence`/`Presence`, `view_once`/`ViewOnce`, `duration`/`Duration`, `number_check`/`NumberCheck`, `context_info`/`ContextInfo`, plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone` and `Audio` are required.
        - `PTT` defaults to **true** (voice note). For PTT a missing, unsupported or `application/octet-stream` type is sent as `audio/ogg; codecs=opus`; Opus content in OGG is always labelled `audio/ogg; codecs=opus`. With `PTT: false` unknown types fall back to `audio/mpeg`.
        - The duration in seconds (1–3600) is computed from the file and a waveform is always generated.
        - `Caption` is accepted but audio messages carry no caption, so it is not delivered.
        - `presence` (ms) shows a **recording audio** indicator and delays the HTTP response by that time.
        - `duration` (seconds) is snapped to 86400, 604800 or 7776000; `number_check: true` verifies the number first.
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together.

        The 200 response adds `Source` (`base64`|`url`), `PTT`, `Type` (`voice_note`|`audio_file`), `Duration` (seconds), `HasWaveform` and the final `MimeType`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageAudio"
            examples:
              voice_note_url:
                summary: Voice note from an HTTPS URL with recording indicator
                value:
                  Phone: "5511999999999"
                  Audio: https://example.com/media/voice-note.ogg
                  PTT: true
                  Presence: 2000
                description: Sends an OGG/Opus file as a voice note (PTT) after a 2 s recording indicator.
              voice_note_base64:
                summary: Voice note as a short base64 data URL
                value:
                  Phone: "5511999999999"
                  Audio: data:audio/ogg;base64,T2dnUwACAAAAAAAAAABK
                  Id: 3EB0C767D26A1D6B8A9F
                description: Sends a short voice note embedded as a data URL with a custom message ID.
              audio_file_snake_case:
                summary: MP3 sent as an audio file (snake_case keys)
                value:
                  phone: "5511988888888"
                  audio: https://example.com/media/podcast-intro.mp3
                  ptt: false
                  number_check: true
                  echo_api: true
                description: "Sends an MP3 as a regular audio file (`ptt: false`) and forces an API echo event."
              group_reply:
                summary: Voice note replying to a group message
                value:
                  Phone: 120363000000000001@g.us
                  Audio: https://example.com/media/reply.ogg
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: Can you explain by audio?
                description: Replies to a group message with a voice note quoting the original text.
      responses:
        "200":
          description: Audio sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        description: Result of a successful send.
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
                          - type: object
                            properties:
                              Duration:
                                type: integer
                                description: Detected duration in seconds.
                                example: 7
                              HasWaveform:
                                type: boolean
                                description: True when a waveform was generated for the voice note.
                                example: true
                              MimeType:
                                type: string
                                description: MIME type written in the message.
                                example: audio/ogg; codecs=opus
                              PTT:
                                type: boolean
                                description: True when sent as a voice note.
                                example: true
                              Source:
                                type: string
                                description: Media input kind (`url` or `base64`).
                                example: url
                              Type:
                                type: string
                                description: "`voice_note` or `audio_file`."
                                example: voice_note
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Duration: 7
                      HasWaveform: true
                      Id: 3EB0C767D26A1D6B8A9F
                      MimeType: audio/ogg; codecs=opus
                      PTT: true
                      Source: url
                      Timestamp: "2026-09-15T10:30:00-03:00"
                      Type: voice_note
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Audio in Payload`
            - `invalid media input: must be either base64 data URL or HTTP(S) URL`
            - `could not decode base64 data:` followed by the base64 decoding error
            - `HTTP error 404 when downloading from URL`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_audio_in_payload:
                  summary: missing Audio in Payload
                  value:
                    code: 400
                    error: missing Audio in Payload
                    success: false
                media_is_neither_data_url_nor_http_s_url:
                  summary: Media is neither data URL nor HTTP(S) URL
                  value:
                    code: 400
                    error: "invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                invalid_base64_data:
                  summary: Invalid base64 data
                  value:
                    code: 400
                    error: "could not decode base64 data: illegal base64 data at input byte 12"
                    success: false
                media_url_returned_404:
                  summary: Media URL returned 404
                  value:
                    code: 400
                    error: HTTP error 404 when downloading from URL
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to upload audio:` followed by the WhatsApp media upload error
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "failed to upload audio: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false
  /chat/send/document:
    post:
      tags:
        - Send
      summary: Sends a document message
      description: |
        Sends a document (any file type) to a contact, group or channel.

        **Media input (`Document`):** a base64 data URL (`data:application/pdf;base64,...`) or an `http://`/`https://` URL downloaded server-side (30 s timeout). No type restriction; the type comes from the data URL header or is detected from the downloaded content. PDFs get page count and a first-page thumbnail when they can be rendered.

        **Accepted keys:** `Phone`, `Document`, `FileName`, `Id`, `MimeType` and `Caption` (matched case-insensitively, so `phone`/`document`/`filename` also work — but not `file_name`), `presence`/`Presence`, `duration`/`Duration`, `number_check`/`NumberCheck`, `ContextInfo` (case-insensitive; `context_info` is not read here), plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone`, `Document` and `FileName` are required (`FileName` is what the recipient sees, include the extension).
        - `MimeType` overrides the detected type in the message.
        - `presence` (ms) shows a typing indicator and delays the HTTP response; `duration` is snapped to 86400, 604800 or 7776000; `number_check: true` verifies the number first.
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together; `ContextInfo` is only applied when it has `stanzaID` (use `mention_info`/`forward_info` for mentions or forwarding without a reply).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageDocument"
            examples:
              pdf_url:
                summary: PDF from an HTTPS URL
                value:
                  Phone: "5511999999999"
                  Document: https://example.com/files/invoice-2026-09.pdf
                  FileName: invoice-2026-09.pdf
                  Caption: Invoice for September
                description: Downloads a PDF server-side and sends it with file name and caption.
              pdf_base64:
                summary: Small PDF as base64 data URL with MIME override
                value:
                  Phone: "5511999999999"
                  Document: data:application/pdf;base64,JVBERi0xLjQKJeLjz9MKMSAwIG9iajw8Pj5lbmRvYmoKdHJhaWxlcjw8Pj4KJSVFT0Y=
                  FileName: contract.pdf
                  MimeType: application/pdf
                  Id: 3EB0C767D26A1D6B8A9F
                description: Sends a small PDF embedded as a data URL and overrides the MIME type.
              group_with_mention:
                summary: Spreadsheet to a group mentioning a member
                value:
                  phone: 120363000000000001@g.us
                  document: https://example.com/files/report.xlsx
                  fileName: report.xlsx
                  caption: Monthly report @5511988888888
                  presence: 1500
                  mention_info:
                    mentioned_jid:
                      - 5511988888888@s.whatsapp.net
                description: Sends a spreadsheet to a group, mentions a member and shows a typing indicator.
              reply:
                summary: Document replying to a message
                value:
                  Phone: "5511988888888"
                  Document: https://example.com/files/terms.pdf
                  FileName: terms.pdf
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: Please send the terms
                description: Sends a document that quotes the message asking for it.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Document in Payload`
            - `missing FileName in Payload`
            - `invalid media input: must be either base64 data URL or HTTP(S) URL`
            - `could not decode base64 data:` followed by the base64 decoding error
            - `HTTP error 404 when downloading from URL`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_document_in_payload:
                  summary: missing Document in Payload
                  value:
                    code: 400
                    error: missing Document in Payload
                    success: false
                missing_filename_in_payload:
                  summary: missing FileName in Payload
                  value:
                    code: 400
                    error: missing FileName in Payload
                    success: false
                media_is_neither_data_url_nor_http_s_url:
                  summary: Media is neither data URL nor HTTP(S) URL
                  value:
                    code: 400
                    error: "invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                invalid_base64_data:
                  summary: Invalid base64 data
                  value:
                    code: 400
                    error: "could not decode base64 data: illegal base64 data at input byte 12"
                    success: false
                media_url_returned_404:
                  summary: Media URL returned 404
                  value:
                    code: 400
                    error: HTTP error 404 when downloading from URL
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to upload file:` followed by the WhatsApp media upload error
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "failed to upload file: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false
  /chat/send/video:
    post:
      tags:
        - Send
      summary: Sends a video message
      description: |
        Sends a video to a contact, group or channel.

        **Media input (`Video`):** a base64 data URL (`data:video/mp4;base64,...`) or an `http://`/`https://` URL downloaded server-side (30 s timeout). The detected type must be `video/mp4`, `video/3gpp`, `video/quicktime` or `video/x-ms-asf` (400 `invalid video format. Supported: MP4, 3GP, MOV, ASF`). For reliable playback use H.264 video with AAC audio.

        **Accepted keys** (either spelling): `phone`/`Phone`, `video`/`Video`, `caption`/`Caption`, `id`/`Id`, `jpeg_thumbnail`/`JPEGThumbnail`, `mime_type`/`MimeType`, `gif`/`GIF`, `presence`/`Presence`, `view_once`/`ViewOnce`, `duration`/`Duration`, `number_check`/`NumberCheck`, `context_info`/`ContextInfo`, plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone` and `Video` are required.
        - `JPEGThumbnail` is a **plain base64** JPEG (no `data:` prefix). When omitted a thumbnail is generated from the video, falling back to a generic image.
        - The length in seconds is read from MP4 files only.
        - `GIF: true` sends it as a looping GIF (videos longer than 6 s may not display as GIF).
        - `MimeType` only overrides the `mimetype` in the message; it does not bypass the format check.
        - `presence` (ms) shows a typing indicator and delays the HTTP response; `duration` is snapped to 86400, 604800 or 7776000; `number_check: true` verifies the number first.
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together; with the PascalCase `ContextInfo` key the object is only applied when it has `stanzaID` (use `mention_info`/`forward_info` otherwise).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageVideo"
            examples:
              url_with_caption:
                summary: MP4 from an HTTPS URL with caption
                value:
                  Phone: "5511999999999"
                  Video: https://example.com/media/tutorial.mp4
                  Caption: Step-by-step setup
                description: Downloads an MP4 server-side and sends it with a caption.
              base64_with_thumbnail:
                summary: Short base64 data URL with explicit thumbnail
                value:
                  Phone: "5511999999999"
                  Video: data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDE=
                  JPEGThumbnail: /9j/4AAQSkZJRgABAQEASABIAAD/wAARCAABAAEBAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/aAAgBAQAAPwAA/9k=
                  MimeType: video/mp4
                description: Sends a short MP4 embedded as a data URL with an explicit JPEG thumbnail.
              gif_snake_case:
                summary: Looping GIF-style video (snake_case keys)
                value:
                  phone: "5511988888888"
                  video: https://example.com/media/celebration.mp4
                  gif: true
                  view_once: false
                  presence: 1500
                  number_check: true
                description: Sends a looping GIF-style video using snake_case keys.
              group_reply:
                summary: Video replying to a group message
                value:
                  Phone: 120363000000000001@g.us
                  Video: https://example.com/media/answer.mp4
                  Caption: As requested
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: Do you have a video of it?
                description: Replies to a group message with a video.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Video in Payload`
            - `invalid video format. Supported: MP4, 3GP, MOV, ASF`
            - `invalid media input: must be either base64 data URL or HTTP(S) URL`
            - `could not decode base64 data:` followed by the base64 decoding error
            - `HTTP error 404 when downloading from URL`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_video_in_payload:
                  summary: missing Video in Payload
                  value:
                    code: 400
                    error: missing Video in Payload
                    success: false
                unsupported_video_type:
                  summary: Unsupported video type
                  value:
                    code: 400
                    error: "invalid video format. Supported: MP4, 3GP, MOV, ASF"
                    success: false
                media_is_neither_data_url_nor_http_s_url:
                  summary: Media is neither data URL nor HTTP(S) URL
                  value:
                    code: 400
                    error: "invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                invalid_base64_data:
                  summary: Invalid base64 data
                  value:
                    code: 400
                    error: "could not decode base64 data: illegal base64 data at input byte 12"
                    success: false
                media_url_returned_404:
                  summary: Media URL returned 404
                  value:
                    code: 400
                    error: HTTP error 404 when downloading from URL
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to upload file:` followed by the WhatsApp media upload error
            - `error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "failed to upload file: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "error sending message: server returned error 479"
                    success: false
  /chat/send/ptv:
    post:
      tags:
        - Send
      summary: Send a round video note (PTV)
      description: |
        Sends a **PTV** message: the round, auto-playing video note, to a contact, group or channel.

        **Rules:**
        - Body: only `Phone` and `Video` (keys matched case-insensitively), plus the optional `EchoApi`/`echoApi`/`echo_api` flag; both are required (`400 missing phone or video`)
        - `Video` is a base64 data URL or an `http://`/`https://` URL downloaded server-side (30 s timeout); errors return `400 error processing video: <reason>`
        - The type is not validated: send a short, square MP4 (H.264/AAC) so the round preview looks right; a JPEG thumbnail is generated from the video

        **Not supported:**
        - Caption, custom `Id` (always generated), `presence`, `duration`, `number_check`, replies (`ContextInfo`), mentions and forwarding
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagePTV"
            examples:
              url:
                summary: Video note from an HTTPS URL
                value:
                  Phone: "5511999999999"
                  Video: https://example.com/media/hello-note.mp4
                description: Downloads a short square MP4 and sends it as a round video note.
              base64:
                summary: Video note as a short base64 data URL
                value:
                  phone: "5511999999999"
                  video: data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDE=
                  EchoApi: true
                description: Sends a video note embedded as a data URL and forces an API echo event.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing phone or video`
            - `could not parse Phone`
            - `error processing video:` followed by the media download or decoding error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_or_video:
                  summary: missing phone or video
                  value:
                    code: 400
                    error: missing phone or video
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                video_cannot_be_processed:
                  summary: Video cannot be processed
                  value:
                    code: 400
                    error: "error processing video: HTTP error 404 when downloading from URL"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error uploading video:` followed by the WhatsApp media upload error
            - `Error sending ptv:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "error uploading video: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending ptv: server returned error 479"
                    success: false
  /chat/send/sticker:
    post:
      tags:
        - Send
      summary: Sends a sticker message
      description: |
        Sends a sticker to a contact, group or channel.

        **Media input (`Sticker`):** a base64 data URL (`data:image/webp;base64,...`) or an `http://`/`https://` URL downloaded server-side (30 s timeout). The detected type must be `image/jpeg`, `image/png`, `image/gif` or `image/webp` (400 `invalid sticker format. Supported: JPEG, PNG, GIF, WebP`). The file is sent **as is** (no conversion or resizing): WhatsApp clients expect a 512×512 WebP, so other formats may not render as a sticker.

        **Accepted keys:** `Phone`, `Sticker`, `Id`, `MimeType`, `PngThumbnail` (case-insensitive), `presence`/`Presence`, `number_check`/`NumberCheck`, `ContextInfo` (case-insensitive), plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone` and `Sticker` are required.
        - `PngThumbnail` is a **plain base64** PNG (no `data:` prefix).
        - No `Duration`/`ViewOnce` support on this endpoint.
        - `presence` (ms) shows a typing indicator and delays the HTTP response; `number_check: true` verifies the number first.
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageSticker"
            examples:
              webp_url:
                summary: WebP sticker from an HTTPS URL
                value:
                  Phone: "5511999999999"
                  Sticker: https://example.com/stickers/thumbs-up.webp
                description: Downloads a WebP sticker server-side and sends it.
              webp_base64:
                summary: WebP sticker as a short base64 data URL
                value:
                  Phone: "5511999999999"
                  Sticker: data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=
                  MimeType: image/webp
                  Id: 3EB0C767D26A1D6B8A9F
                description: Sends a WebP sticker embedded as a data URL with a custom message ID.
              group_reply:
                summary: Sticker replying in a group
                value:
                  phone: 120363000000000001@g.us
                  sticker: https://example.com/stickers/party.webp
                  number_check: false
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: We closed the deal!
                description: Replies to a group message with a sticker.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Sticker in Payload`
            - `invalid sticker format. Supported: JPEG, PNG, GIF, WebP`
            - `invalid media input: must be either base64 data URL or HTTP(S) URL`
            - `could not decode base64 data:` followed by the base64 decoding error
            - `HTTP error 404 when downloading from URL`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_sticker_in_payload:
                  summary: missing Sticker in Payload
                  value:
                    code: 400
                    error: missing Sticker in Payload
                    success: false
                unsupported_sticker_type:
                  summary: Unsupported sticker type
                  value:
                    code: 400
                    error: "invalid sticker format. Supported: JPEG, PNG, GIF, WebP"
                    success: false
                media_is_neither_data_url_nor_http_s_url:
                  summary: Media is neither data URL nor HTTP(S) URL
                  value:
                    code: 400
                    error: "invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                invalid_base64_data:
                  summary: Invalid base64 data
                  value:
                    code: 400
                    error: "could not decode base64 data: illegal base64 data at input byte 12"
                    success: false
                media_url_returned_404:
                  summary: Media URL returned 404
                  value:
                    code: 400
                    error: HTTP error 404 when downloading from URL
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `Failed to upload file:` followed by the WhatsApp media upload error
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "Failed to upload file: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false
  /chat/send/location:
    post:
      tags:
        - Send
      summary: Send a location pin
      description: |
        Sends a static location (map pin) to a contact or group.

        **Accepted keys:** `Phone`, `Latitude`, `Longitude`, `Name`, `Id` (matched case-insensitively, so `latitude`/`longitude` also work), `presence`/`Presence`, `duration`/`Duration`, `number_check`/`NumberCheck`, `ContextInfo` (case-insensitive), plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone`, `Latitude` and `Longitude` are required. Coordinates are decimal degrees (numbers, not strings).
        - A coordinate equal to **exactly 0** is treated as missing (400 `missing Latitude in Payload` / `missing Longitude in Payload`), so points on the Equator or the prime meridian cannot be sent as-is.
        - `Name` is an optional label shown with the pin; there is no separate address field.
        - `Id` sets a custom message ID; otherwise one is generated.
        - `presence` (ms) shows a typing indicator and delays the HTTP response.
        - `duration` is accepted but **not applied** on this endpoint.
        - `number_check: true` verifies the number first.
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together. Mentions via `mention_info`; forwarding via `forward_info.is_forwarded`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageLocation"
            examples:
              basic:
                summary: Location with a name
                value:
                  Phone: "5511999999999"
                  Latitude: -23.561414
                  Longitude: -46.655881
                  Name: Example Store - Paulista Ave
                description: Sends a named location pin.
              lowercase_keys:
                summary: Lowercase keys with typing delay and number check
                value:
                  phone: "5511988888888"
                  latitude: -22.951916
                  longitude: -43.210487
                  name: Meeting point
                  presence: 1500
                  number_check: true
                description: Uses lowercase keys, checks the number and shows a typing indicator first.
              group_reply:
                summary: Location replying in a group
                value:
                  Phone: 120363000000000001@g.us
                  Latitude: -25.428356
                  Longitude: -49.273252
                  Name: Warehouse
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511988888888@s.whatsapp.net
                    quotedMessage:
                      conversation: Where do we pick it up?
                description: Replies to a group question with the pickup location.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Latitude in Payload`
            - `missing Longitude in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_latitude_in_payload:
                  summary: missing Latitude in Payload
                  value:
                    code: 400
                    error: missing Latitude in Payload
                    success: false
                missing_longitude_in_payload:
                  summary: missing Longitude in Payload
                  value:
                    code: 400
                    error: missing Longitude in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "error sending message: server returned error 479"
                    success: false
  /chat/send/contact:
    post:
      tags:
        - Send
      summary: Send a contact card (vCard)
      description: |
        Sends a contact card to a contact or group. The recipient sees `Name` and can open or save the card.

        **Accepted keys:** `Phone`, `Name`, `Vcard`, `Id` (matched case-insensitively), `presence`/`Presence`, `duration`/`Duration`, `number_check`/`NumberCheck`, `ContextInfo` (case-insensitive), plus `mention_info`, `forward_info` and `EchoApi`/`echoApi`/`echo_api`.

        **Rules:**
        - `Phone`, `Name` and `Vcard` are required (400 `missing Name in Payload` / `missing Vcard in Payload`).
        - `Name` becomes the card display name. `Vcard` is sent **verbatim** (not validated); use vCard 3.0 with `\n` line breaks. Add `waid=<number>` to the `TEL` line so WhatsApp links the card to the account (Message/Add buttons).
        - `Id` sets a custom message ID; otherwise one is generated.
        - `presence` (ms) shows a typing indicator and delays the HTTP response.
        - `duration` is accepted but **not applied** on this endpoint.
        - `number_check: true` verifies the number first (400 `phone number is not registered on WhatsApp`).
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together (400 `missing Participant in ContextInfo`). Mentions via `mention_info`; forwarding via `forward_info.is_forwarded`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageContact"
            examples:
              basic:
                summary: Share a contact with WhatsApp link
                value:
                  Phone: "5511999999999"
                  Name: Maria Souza
                  Vcard: |-
                    BEGIN:VCARD
                    VERSION:3.0
                    N:Souza;Maria;;;
                    FN:Maria Souza
                    ORG:Example Ltda
                    TEL;type=CELL;waid=5511988888888:+55 11 98888-8888
                    EMAIL;type=INTERNET:maria@example.com
                    END:VCARD
                description: Shares a vCard whose `waid` makes the contact open directly in WhatsApp.
              reply_snake_case:
                summary: Contact card replying to a message, with number check
                value:
                  phone: "5511977777777"
                  name: Support Team
                  vcard: |-
                    BEGIN:VCARD
                    VERSION:3.0
                    FN:Support Team
                    TEL;type=WORK;waid=5511988888888:+55 11 98888-8888
                    END:VCARD
                  number_check: true
                  presence: 1000
                  ContextInfo:
                    stanzaID: 3EB0A1B2C3D4E5F60718
                    participant: 5511977777777@s.whatsapp.net
                    quotedMessage:
                      conversation: Who can help me?
                description: Replies with a support contact card using snake_case keys and number check.
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Name in Payload`
            - `missing Vcard in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_name_in_payload:
                  summary: missing Name in Payload
                  value:
                    code: 400
                    error: missing Name in Payload
                    success: false
                missing_vcard_in_payload:
                  summary: missing Vcard in Payload
                  value:
                    code: 400
                    error: missing Vcard in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "error sending message: server returned error 479"
                    success: false
  /chat/send/list:
    post:
      tags:
        - Send
      summary: Send a list message
      description: |
        Sends a single-select **list message**: a text body plus a button that opens sections of selectable rows. The user's choice arrives later as an incoming `Message` event (`listResponseMessage`) carrying the selected `RowId`.

        **Accepted keys** (either spelling): `phone`/`Phone`, `text`/`Text`, `title`/`Title`, `footer`/`Footer`, `buttonText`/`ButtonText`, `sections`/`Sections`, `id`/`Id`, `presence`/`Presence`, `duration`/`Duration`, `number_check`/`NumberCheck`, `context_info`/`ContextInfo`, plus `EchoApi`/`echoApi`/`echo_api`. Section: `title`/`Title`, `rows`/`Rows`. Row: `rowId`/`RowId`/`RowID`, `title`/`Title`, `description`/`Description`.

        **Validation (lengths are counted in bytes):**
        - `Phone`, `Text` and `ButtonText` are required (400 `missing Text in Payload`, `missing ButtonText in Payload`).
        - At least one section, and each section needs at least one row (400 `empty Array sections for list message`, `rows can´t be empty`).
        - `Title` ≤ 60 (`title max 60 characters`), `Text` ≤ 1024 (`body max 1024 characters`), `ButtonText` ≤ 20 (`buttonDisplay max 20 characters`), `Footer` ≤ 60 (`footer max 60 characters`), section `Title` ≤ 60.
        - The gateway does not limit the number of sections/rows nor row title/description length; WhatsApp clients apply their own limits. Give every row a unique `RowId`.

        **Other rules:**
        - Message ID: when the server has `LICENSE_KEY` set, the ID is `<yyyyMMddHHmmss><last segment of the license key>` and `Id` is **ignored**; otherwise `Id` is used or a random ID is generated.
        - `presence` (ms) shows a typing indicator and delays the HTTP response; `duration` is snapped to 86400, 604800 or 7776000; `number_check: true` verifies the number first.
        - Replies need `ContextInfo.stanzaID` + `ContextInfo.participant` together; `ContextInfo.mention_all` in groups mentions every member.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageList"
            examples:
              product_menu:
                summary: Plans and services menu
                description: Interactive list with two sections of rows.
                value:
                  Phone: "5511988888888"
                  Title: 2026 Plans
                  Text: See this month's highlights
                  Footer: Choose an option below
                  ButtonText: See options
                  Sections:
                    - Title: Available plans
                      Rows:
                        - RowId: plan_basic
                          Title: Basic plan
                          Description: Ideal for small teams
                        - RowId: plan_pro
                          Title: Pro plan
                          Description: Advanced features and priority support
                    - Title: Additional services
                      Rows:
                        - RowId: service_consulting
                          Title: Consulting
                          Description: 1:1 sessions with specialists
                        - RowId: service_training
                          Title: Training
                          Description: Custom workshops for teams
              restaurant_menu:
                summary: Restaurant menu in a group
                description: Menu sections and dishes sent to a group.
                value:
                  Phone: 120363000000000011@g.us
                  Title: Weekly special menu
                  Text: Select your dish below
                  Footer: Open from 11 AM to 11 PM
                  ButtonText: Choose a dish
                  Sections:
                    - Title: Starters
                      Rows:
                        - RowId: starter_01
                          Title: Bruschetta
                          Description: Tomato, basil and olive oil
                        - RowId: starter_02
                          Title: Carpaccio
                          Description: Thin beef slices with sauce
                    - Title: Main courses
                      Rows:
                        - RowId: main_01
                          Title: Shrimp risotto
                          Description: Arborio rice and grilled shrimp
                        - RowId: main_02
                          Title: Filet mignon
                          Description: Served with gratin potatoes
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Text in Payload`
            - `missing ButtonText in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
            - `rows can´t be empty`
            - `title max 60 characters`
            - `buttonDisplay max 20 characters`
            - `body max 1024 characters`
            - `footer max 60 characters`
            - `empty Array sections for list message`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_text_in_payload:
                  summary: missing Text in Payload
                  value:
                    code: 400
                    error: missing Text in Payload
                    success: false
                missing_buttontext_in_payload:
                  summary: missing ButtonText in Payload
                  value:
                    code: 400
                    error: missing ButtonText in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
                section_without_rows:
                  summary: Section without rows
                  value:
                    code: 400
                    error: rows can´t be empty
                    success: false
                title_or_section_title_longer_than_60_characters:
                  summary: Title or section title longer than 60 characters
                  value:
                    code: 400
                    error: title max 60 characters
                    success: false
                buttontext_longer_than_20_characters:
                  summary: ButtonText longer than 20 characters
                  value:
                    code: 400
                    error: buttonDisplay max 20 characters
                    success: false
                text_longer_than_1024_characters:
                  summary: Text longer than 1024 characters
                  value:
                    code: 400
                    error: body max 1024 characters
                    success: false
                footer_longer_than_60_characters:
                  summary: Footer longer than 60 characters
                  value:
                    code: 400
                    error: footer max 60 characters
                    success: false
                no_sections:
                  summary: No sections
                  value:
                    code: 400
                    error: empty Array sections for list message
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false
  /chat/send/poll:
    post:
      tags:
        - Send
      summary: Sends a Poll to some group
      description: |
        Sends a poll (single or multiple choice) to a group or directly to a contact.

        **Features:**
        - Question in `Header` (or its alias `Question`; `Header` wins when both are sent) with at least 2 `Options`
        - `MaxAnswer` limits how many options each participant can choose (default 1)
        - Optional typing indicator (`Presence`), number check, disappearing `Duration`, replies, mentions and forwarding

        **Rules:**
        - `Group` (group JID such as `120363000000000011@g.us`) or `Phone` is required (`400 missing Group or Phone in payload`); `Group` wins when both are sent
        - `400 missing Header or Question in payload`, `at least 2 options are required`, `max_answer must be at least 1`, `max_answer cannot be greater than options length (<n>)`
        - Vote from this session with `POST /chat/send/pollvote`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagePoll"
            examples:
              group_poll:
                summary: Poll in a group
                description: Multi-answer poll sent to a group with a typing indicator.
                value:
                  Group: 120363000000000011@g.us
                  Header: What is the best time?
                  Options:
                    - "09:00"
                    - "14:00"
                    - "19:00"
                  MaxAnswer: 2
                  Presence: 2000
                  NumberCheck: true
              direct_poll:
                summary: Direct poll to a contact
                description: Single-answer poll sent directly to a contact with a 24 h disappearing timer.
                value:
                  Phone: "5511988888888"
                  Question: Would you recommend our support?
                  Options:
                    - "Yes"
                    - Maybe
                    - "No"
                  MaxAnswer: 1
                  Duration: 86400
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Result of a successful poll send.
                        properties:
                          Details:
                            type: string
                            description: Always `Poll sent successfully`.
                            example: Poll sent successfully
                          Id:
                            type: string
                            description: WhatsApp message ID of the poll.
                            example: 3EB0C767D26A1D6B8A9F
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Poll sent successfully
                      Id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode payload`
            - `missing Group or Phone in payload`
            - `missing Header or Question in payload`
            - `at least 2 options are required`
            - `max_answer must be at least 1`
            - `max_answer cannot be greater than options length (3)`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode payload
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_group_or_phone_in_payload:
                  summary: missing Group or Phone in payload
                  value:
                    code: 400
                    error: missing Group or Phone in payload
                    success: false
                missing_header_or_question_in_payload:
                  summary: missing Header or Question in payload
                  value:
                    code: 400
                    error: missing Header or Question in payload
                    success: false
                at_least_2_options_are_required:
                  summary: at least 2 options are required
                  value:
                    code: 400
                    error: at least 2 options are required
                    success: false
                max_answer_must_be_at_least_1:
                  summary: max_answer must be at least 1
                  value:
                    code: 400
                    error: max_answer must be at least 1
                    success: false
                max_answer_above_the_number_of_options:
                  summary: max_answer above the number of options
                  value:
                    code: 400
                    error: max_answer cannot be greater than options length (3)
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to send poll:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "failed to send poll: server returned error 479"
                    success: false
  /chat/send/pollvote:
    post:
      tags:
        - Send
      summary: Vote on a poll
      description: |
        Casts or retracts this session's vote on a poll.

        **Rules:**
        - The poll is identified by the message ID of its creation message (`poll_message_id`)
        - Option names must match the poll's options exactly; unknown options are rejected with `400` instead of being sent as an empty vote
        - Multi-select polls accept more than one option, up to the poll's own limit
        - An empty `options: []` array retracts the vote
        - The vote is encrypted with the poll's message secret, so the poll must have been sent or received by this session (`404` otherwise)
        - Polls created before this instance recorded poll creator info cannot be voted on (`409`); a vote is never sent with a guessed identity, which WhatsApp would silently drop
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PollVote"
            examples:
              single_choice:
                summary: Vote on a single-choice poll
                value:
                  phone: "5511999999999"
                  poll_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  options:
                    - "09:00"
                description: Votes for one option of a single-choice poll.
              multi_choice:
                summary: Vote on a multi-select poll
                value:
                  phone: 120363000000000011@g.us
                  poll_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  options:
                    - "09:00"
                    - "14:00"
                description: Votes for two options of a multi-select poll in a group.
              retract:
                summary: Retract the vote (empty options)
                value:
                  phone: "5511999999999"
                  poll_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  options: []
                description: Sends an empty options list to retract the previous vote.
      responses:
        "200":
          description: Vote sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        description: Result of a successful send.
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
                          - type: object
                            properties:
                              Options:
                                type: array
                                description: Options voted (empty when the vote was retracted).
                                items:
                                  type: string
                                example:
                                  - "09:00"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26B8F4A1C42
                      Options:
                        - "09:00"
                      Timestamp: "2026-07-13T18:22:41Z"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing phone in Payload`
            - `missing poll_message_id in Payload`
            - `could not parse Phone`
            - `option "20:00" is not one of this poll's options: [09:00 14:00 19:00]`
            - `poll allows at most 1 selected option(s), got 2`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing phone in Payload
                  value:
                    code: 400
                    error: missing phone in Payload
                    success: false
                missing_poll_message_id_in_payload:
                  summary: missing poll_message_id in Payload
                  value:
                    code: 400
                    error: missing poll_message_id in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                unknown_option:
                  summary: Unknown option
                  value:
                    code: 400
                    error: "option \"20:00\" is not one of this poll's options: [09:00 14:00 19:00]"
                    success: false
                too_many_options_selected:
                  summary: Too many options selected
                  value:
                    code: 400
                    error: poll allows at most 1 selected option(s), got 2
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: |
            The referenced message is unknown to this session. Possible `error` values:
            - `unknown poll_message_id: this session never sent or received that poll`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unknown_poll:
                  summary: Unknown poll
                  value:
                    code: 404
                    error: "unknown poll_message_id: this session never sent or received that poll"
                    success: false
        "409":
          description: |
            The message cannot be answered by this session. Possible `error` values:
            - `this poll predates vote support (its creator info was never recorded) and cannot be voted on`
            - `the poll's message secret is unknown to this session, so its vote cannot be encrypted`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                poll_predates_vote_support:
                  summary: Poll predates vote support
                  value:
                    code: 409
                    error: this poll predates vote support (its creator info was never recorded) and cannot be voted on
                    success: false
                poll_secret_unknown:
                  summary: Poll secret unknown
                  value:
                    code: 409
                    error: the poll's message secret is unknown to this session, so its vote cannot be encrypted
                    success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to load poll metadata`
            - `failed to build poll vote:` followed by the vote encryption error
            - `error sending poll vote:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                failed_to_load_poll_metadata:
                  summary: failed to load poll metadata
                  value:
                    code: 500
                    error: failed to load poll metadata
                    success: false
                vote_cannot_be_built:
                  summary: Vote cannot be built
                  value:
                    code: 500
                    error: "failed to build poll vote: original message secret key not found"
                    success: false
                whatsapp_rejected_the_vote:
                  summary: WhatsApp rejected the vote
                  value:
                    code: 500
                    error: "error sending poll vote: server returned error 479"
                    success: false
  /chat/send/eventresponse:
    post:
      tags:
        - Send
      summary: Respond (RSVP) to a calendar event
      description: |
        Sends an RSVP to a calendar event message.

        **Rules:**
        - The event is identified by the message ID of its creation message (`event_message_id`)
        - `response` is `GOING`, `NOT_GOING`, `MAYBE` or `UNKNOWN`; anything else returns `400`. `UNKNOWN` removes a previous RSVP
        - The RSVP is encrypted with the event's message secret, so the event must have been sent or received by this session (`404` otherwise)
        - Events created before this instance recorded event creator info cannot be answered (`409`)

        **Guest:**
        - Send `with_guest: true` (alias `WithGuest`) for "Going with guest"; the official app sends it as `extraGuestCount=1`
        - The guest only applies to `GOING` and is ignored for other responses; `with_guest` takes precedence over `extra_guest_count`
        - `extra_guest_count` is deprecated but accepted: `0` means no guest and values of 1 or more are sent as one guest; negative values return `400`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EventResponse"
            examples:
              going:
                summary: Confirm attendance
                value:
                  phone: "5511999999999"
                  event_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  response: GOING
                  with_guest: false
                description: Confirms attendance without a guest.
              going_with_guest:
                summary: Going with guest
                value:
                  phone: "5511999999999"
                  event_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  response: GOING
                  with_guest: true
                description: Confirms attendance with one guest.
              not_going:
                summary: Decline the invitation
                value:
                  phone: "5511999999999"
                  event_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  response: NOT_GOING
                description: Declines the event invitation.
              remove:
                summary: Remove the RSVP (UNKNOWN)
                value:
                  phone: "5511999999999"
                  event_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  response: UNKNOWN
                description: Removes the previous RSVP by sending `UNKNOWN`.
              legacy_extra_guest_count:
                summary: Legacy extra_guest_count (deprecated)
                value:
                  phone: "5511999999999"
                  event_message_id: 90B2F8B13FAC8A9CF6B06E99C7834DC5
                  response: GOING
                  extra_guest_count: 1
                description: "Deprecated field: any `extra_guest_count` of 1 or more is sent as a single guest."
      responses:
        "200":
          description: RSVP sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        description: Result of a successful send.
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
                          - type: object
                            properties:
                              Response:
                                type: string
                                description: RSVP sent.
                                example: GOING
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26B8F4A1C42
                      Response: GOING
                      Timestamp: "2026-07-13T18:22:41Z"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing phone in Payload`
            - `missing event_message_id in Payload`
            - `missing response in Payload`
            - `extra_guest_count cannot be negative`
            - `could not parse Phone`
            - `response must be one of GOING, NOT_GOING, MAYBE or UNKNOWN`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing phone in Payload
                  value:
                    code: 400
                    error: missing phone in Payload
                    success: false
                missing_event_message_id_in_payload:
                  summary: missing event_message_id in Payload
                  value:
                    code: 400
                    error: missing event_message_id in Payload
                    success: false
                missing_response_in_payload:
                  summary: missing response in Payload
                  value:
                    code: 400
                    error: missing response in Payload
                    success: false
                extra_guest_count_cannot_be_negative:
                  summary: extra_guest_count cannot be negative
                  value:
                    code: 400
                    error: extra_guest_count cannot be negative
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                unknown_response_value:
                  summary: Unknown response value
                  value:
                    code: 400
                    error: response must be one of GOING, NOT_GOING, MAYBE or UNKNOWN
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: |
            The referenced message is unknown to this session. Possible `error` values:
            - `unknown event_message_id: this session never sent or received that event`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unknown_event:
                  summary: Unknown event
                  value:
                    code: 404
                    error: "unknown event_message_id: this session never sent or received that event"
                    success: false
        "409":
          description: |
            The message cannot be answered by this session. Possible `error` values:
            - `this event predates RSVP support (its creator info was never recorded) and cannot be answered`
            - `the event's message secret is unknown to this session, so its RSVP cannot be encrypted`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                event_predates_rsvp_support:
                  summary: Event predates RSVP support
                  value:
                    code: 409
                    error: this event predates RSVP support (its creator info was never recorded) and cannot be answered
                    success: false
                event_secret_unknown:
                  summary: Event secret unknown
                  value:
                    code: 409
                    error: the event's message secret is unknown to this session, so its RSVP cannot be encrypted
                    success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to load event metadata`
            - `failed to build event response:` followed by the RSVP encryption error
            - `error sending event response:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                failed_to_load_event_metadata:
                  summary: failed to load event metadata
                  value:
                    code: 500
                    error: failed to load event metadata
                    success: false
                rsvp_cannot_be_built:
                  summary: RSVP cannot be built
                  value:
                    code: 500
                    error: "failed to build event response: failed to encrypt event response: original message secret key not found"
                    success: false
                whatsapp_rejected_the_rsvp:
                  summary: WhatsApp rejected the RSVP
                  value:
                    code: 500
                    error: "error sending event response: server returned error 479"
                    success: false
  /chat/send/event:
    post:
      tags:
        - Send
      summary: Send event message
      description: |
        Send calendar event invitation with date, time, location, and description to a WhatsApp contact or group.
        Recipients can add the event to their calendar. Perfect for meetings, appointments, conferences, and scheduled calls.

        **Features:**
        - **Event Details**: Name, description, start/end times
        - **Location Support**: GPS coordinates and address
        - **Call Integration**: Schedule video/voice meetings
        - **Guest Management**: Allow additional guests
        - **Cancellation**: Mark events as cancelled
        - **Message Enhancements**: Replies, forwarding, mentions, expiration

        **Use Cases:**
        - 📅 Meeting invitations
        - 🎉 Event planning (parties, conferences)
        - 📞 Scheduled calls
        - 🏢 Location-based events
        - ❌ Event cancellations
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageEvent"
            examples:
              business_meeting:
                summary: Business Meeting
                description: Schedule a business meeting with location
                value:
                  Phone: "5511999999999"
                  Name: Weekly Team Sync 📅
                  Description: Discussion of project updates, blockers, and next steps
                  StartTime: 2524608000
                  EndTime: 2556143999
                  ExtraGuestsAllowed: true
                  IsScheduleCall: true
                  Location:
                    Name: Conference Room A, Main Office
                    DegreesLatitude: -23.5505
                    DegreesLongitude: -46.6333
                  NumberCheck: true
                  ContextInfo:
                    MentionedJID:
                      - 5511999999999@s.whatsapp.net
              virtual_event:
                summary: Virtual Event
                description: Online event without physical location
                value:
                  Phone: 120363000000000011@g.us
                  Name: Product Launch Event 🚀
                  Description: Join us for the launch of our new product line
                  StartTime: 1703184000
                  EndTime: 1703191200
                  ExtraGuestsAllowed: true
                  IsScheduleCall: true
                  MentionInfo:
                    MentionAll: true
      responses:
        "200":
          description: Event message sent successfully
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26A1D6B8A9F
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `Could not decode Payload`
            - `Missing phone in Payload`
            - `Missing name in Payload`
            - `Missing start_time in Payload`
            - `Could not parse JID`
            - `Number is not on WhatsApp`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: Could not decode Payload
                  value:
                    code: 400
                    error: Could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: Missing phone in Payload
                  value:
                    code: 400
                    error: Missing phone in Payload
                    success: false
                missing_name_in_payload:
                  summary: Missing name in Payload
                  value:
                    code: 400
                    error: Missing name in Payload
                    success: false
                missing_start_time_in_payload:
                  summary: Missing start_time in Payload
                  value:
                    code: 400
                    error: Missing start_time in Payload
                    success: false
                could_not_parse_jid:
                  summary: Could not parse JID
                  value:
                    code: 400
                    error: Could not parse JID
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: Number is not on WhatsApp
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `No session`
            - `Failed to check if 5511999999999 is on WhatsApp:` followed by the phone number and the WhatsApp lookup error
            - `Error sending event:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: No session
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 500
                    error: "Failed to check if 5511999999999 is on WhatsApp: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending event: server returned error 479"
                    success: false

  /chat/send/buttons:
    post:
      tags:
        - Send
      summary: Send interactive buttons message
      description: |
        Send an interactive message with buttons to a WhatsApp contact or group. Supports multiple button types and media headers.

        **⚠️ IMPORTANT - Text fields (canonical casing):**
        - Payloads should use `lowerCamelCase` (e.g. `buttonId`, `displayText`) and, where applicable, the optional snake_case fields such as `pix_key`.
        - With a **media header** (`image`/`video`/`document`): put the main content in `caption` or `text`.
        - **Without** media: use `text` or `body`. The handler mirrors one into the other automatically.
        - Media examples use `text` for backend compatibility.

        **Supported Button Types:**
        - 🔘 **reply/quick_reply** – quick reply button (returns `buttonId`)
        - 🔗 **url/cta_url** – opens an HTTP(S) link (`url` or `merchant_url`)
        - 📞 **call/cta_call** – starts a voice call (`phone` in E.164 format)
        - 📋 **copy/cta_copy** – copies a code (`code`)
        - 💰 **pix/payment_info** – PIX payment button (`pix_key`, `merchant_name`, `pix_type`, ...)
        - 💳 **review_and_pay** – review and pay with an item list (`items[]`)

        A reply/quick_reply button cannot be mixed with other button types in the same message (400 `if a ButtonReply is present, other button types are not allowed`).

        **Media Header Support:**
        - 🖼️ `image` – shows an image above the buttons (`image.url` required)
        - 🎥 `video` – shows a short video (`video.url`)
        - 📄 `document` – shows a document (`document.url`)

        **Button Configuration:**
        Each button requires `buttonId`, `buttonText.displayText` and `type`.
        Additional fields depend on the type:
        - **url/cta_url**: `url` or `merchant_url`
        - **call/cta_call**: `phone`
        - **copy/cta_copy**: `code`
        - **pix/payment_info**: `pix_key`, `merchant_name`, `pix_type`, `currency`, `total_value`, `total_offset`
        - **review_and_pay**: `reference_id` and `items[]` with `name`, `quantity`, `amount_value`, `amount_offset`

        **Payment Items Structure** (`review_and_pay`):
        Each item must provide `name`, `quantity`, `amount_value`, `amount_offset`.

        **Text Fields:**
        - **title**: Required main title (1 to 60 characters). Do not send only spaces: WhatsApp would show an empty header area.
        - **text/body**: Message content (required when there is no media)
        - **footer**: Secondary text (optional)
        - **caption**: Used as the body when sending media

        Requires an Enterprise license (403 otherwise).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ButtonsMessage"
            examples:
              pix_payment:
                summary: PIX payment button
                description: PIX key button using the camelCase/snake_case payload accepted by the API.
                value:
                  phone: "5511988888888"
                  title: PIX payment
                  body: Scan the QR code to pay with PIX
                  footer: Secure instant payment
                  buttons:
                    - buttonId: pix_btn_1
                      buttonText:
                        displayText: Pay with PIX
                      type: pix
                      pix_key: "11999999999"
                      merchant_name: Example Store
                      pix_type: PHONE
              review_and_pay:
                summary: Review and pay
                description: Order review with an item list and PIX payment (amounts in minor units).
                value:
                  phone: "5511988888888"
                  title: Review your order
                  text: Confirm the items before paying
                  footer: Ships within 24 hours after confirmation
                  buttons:
                    - buttonId: review_pay_btn
                      buttonText:
                        displayText: Review and pay
                      type: review_and_pay
                      pix_key: "11999999999"
                      merchant_name: Example Store
                      pix_type: PHONE
                      currency: BRL
                      total_value: 10990
                      total_offset: 100
                      reference_id: ORDER-2026-001
                      items:
                        - name: Headphones
                          quantity: 1
                          amount_value: 7990
                          amount_offset: 100
                        - name: USB-C cable
                          quantity: 1
                          amount_value: 1990
                          amount_offset: 100
                        - name: Express shipping
                          quantity: 1
                          amount_value: 1000
                          amount_offset: 100
              pix_with_pdf:
                summary: PIX with a PDF invoice
                description: "review_and_pay button with a PDF document header, suited to invoices and bills. Supported pix_type values: CPF, CNPJ, EMAIL, PHONE, EVP (random key)."
                value:
                  phone: "5511988888888"
                  title: Monthly invoice
                  text: Your invoice is available. Pay with PIX.
                  footer: "Due date: 2026-10-15"
                  document:
                    url: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
                  buttons:
                    - buttonId: pix_invoice_1
                      buttonText:
                        displayText: Pay invoice
                      type: review_and_pay
                      pix_key: billing@example.com
                      merchant_name: Example Company Ltd
                      pix_type: EMAIL
                      currency: BRL
                      total_value: 29990
                      total_offset: 100
                      reference_id: INV-2026-09-001
                      items:
                        - name: September subscription
                          quantity: 1
                          amount_value: 24990
                          amount_offset: 100
                        - name: Service fee
                          quantity: 1
                          amount_value: 5000
                          amount_offset: 100
              pix_evp:
                summary: PIX with a random key (EVP)
                description: PIX button with a random EVP key, suited to one-off payments.
                value:
                  phone: "5511988888888"
                  title: PIX payment
                  body: Copy the PIX key and pay in your banking app
                  footer: Secure instant payment
                  buttons:
                    - buttonId: pix_evp_1
                      buttonText:
                        displayText: Copy PIX key
                      type: pix
                      pix_key: 123e4567-e89b-12d3-a456-426614174000
                      merchant_name: Example Store
                      pix_type: EVP
              video_cta_url:
                summary: Video header with a URL button
                description: Product demo video with a button that opens the offer page.
                value:
                  phone: "5511988888888"
                  title: Product demo
                  text: Watch the video and buy!
                  footer: Instant payment
                  video:
                    url: https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
                  buttons:
                    - buttonId: cta_url_1
                      buttonText:
                        displayText: Open site
                      type: cta_url
                      url: https://example.com/offer
              document_cta_url:
                summary: PDF catalog with a URL button
                description: Catalog download with a CTA URL button.
                value:
                  phone: "5511988888888"
                  title: Product catalog
                  text: Download our catalog
                  footer: PDF with every product
                  document:
                    url: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
                  buttons:
                    - buttonId: cta_url_1
                      buttonText:
                        displayText: Open site
                      type: cta_url
                      url: https://example.com/offer
              image_quick_reply:
                summary: Image header with a quick reply
                description: Attendance confirmation using a quick reply button.
                value:
                  phone: "5511988888888"
                  title: Confirm your attendance
                  text: VIP event on Oct 12 at 8 PM
                  footer: Reply below
                  image:
                    url: https://picsum.photos/600/400.jpg
                  buttons:
                    - buttonId: reply_maybe
                      buttonText:
                        displayText: Maybe
                      type: quick_reply
              multi_action:
                summary: Copy + URL + call buttons
                description: Mixes copy code, URL and call buttons in one message.
                value:
                  phone: "5511988888888"
                  title: Deals of the day
                  text: "Choose an action below:"
                  footer: ZuckZapGo team
                  buttons:
                    - buttonId: cta_copy_1
                      buttonText:
                        displayText: Copy coupon
                      type: cta_copy
                      code: COUPON123
                    - buttonId: cta_url_1
                      buttonText:
                        displayText: Open site
                      type: cta_url
                      url: https://example.com/offer
                    - buttonId: cta_call_1
                      buttonText:
                        displayText: Call sales
                      type: cta_call
                      phone: "+5511988888888"
      responses:
        "200":
          description: Buttons message sent successfully
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26A1D6B8A9F
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
            - `button type not found:` followed by the unknown `type` value
            - `button type not specified`
            - `url required`
            - `url example.com/offer is not valid`
            - `text can´t be null`
            - `text max 30 characters`
            - `phone required`
            - `code required`
            - `id required`
            - `BANK is not pix type, valid type: PHONE, CPF, CNPJ, EMAIL and EVP`
            - `error processing media:` followed by the header media download or decoding error
            - `no body text provided`
            - `header can´t be null`
            - `title max 60 characters`
            - `text max 1024 characters`
            - `footer max 1024 characters`
            - `empty Array buttons`
            - `3 buttons is the limit`
            - `if a ButtonReply is present, other button types are not allowed`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
                unknown_button_type:
                  summary: Unknown button type
                  value:
                    code: 400
                    error: "button type not found: carousel"
                    success: false
                button_without_type:
                  summary: Button without type
                  value:
                    code: 400
                    error: button type not specified
                    success: false
                url_button_without_url:
                  summary: URL button without url
                  value:
                    code: 400
                    error: url required
                    success: false
                invalid_button_url:
                  summary: Invalid button URL
                  value:
                    code: 400
                    error: url example.com/offer is not valid
                    success: false
                button_without_displaytext:
                  summary: Button without displayText
                  value:
                    code: 400
                    error: text can´t be null
                    success: false
                displaytext_longer_than_30_characters:
                  summary: displayText longer than 30 characters
                  value:
                    code: 400
                    error: text max 30 characters
                    success: false
                call_button_without_phone:
                  summary: Call button without phone
                  value:
                    code: 400
                    error: phone required
                    success: false
                copy_button_without_code:
                  summary: Copy button without code
                  value:
                    code: 400
                    error: code required
                    success: false
                reply_button_without_buttonid:
                  summary: Reply button without buttonId
                  value:
                    code: 400
                    error: id required
                    success: false
                unknown_pix_type:
                  summary: Unknown pix_type
                  value:
                    code: 400
                    error: "BANK is not pix type, valid type: PHONE, CPF, CNPJ, EMAIL and EVP"
                    success: false
                header_media_cannot_be_processed:
                  summary: Header media cannot be processed
                  value:
                    code: 400
                    error: "error processing media: HTTP error 404 when downloading from URL"
                    success: false
                no_body_text:
                  summary: No body text
                  value:
                    code: 400
                    error: no body text provided
                    success: false
                empty_title:
                  summary: Empty title
                  value:
                    code: 400
                    error: header can´t be null
                    success: false
                title_longer_than_60_characters:
                  summary: Title longer than 60 characters
                  value:
                    code: 400
                    error: title max 60 characters
                    success: false
                body_longer_than_1024_characters:
                  summary: Body longer than 1024 characters
                  value:
                    code: 400
                    error: text max 1024 characters
                    success: false
                footer_longer_than_1024_characters:
                  summary: Footer longer than 1024 characters
                  value:
                    code: 400
                    error: footer max 1024 characters
                    success: false
                no_buttons:
                  summary: No buttons
                  value:
                    code: 400
                    error: empty Array buttons
                    success: false
                more_than_3_buttons:
                  summary: More than 3 buttons
                  value:
                    code: 400
                    error: 3 buttons is the limit
                    success: false
                reply_mixed_with_other_button_types:
                  summary: Reply mixed with other button types
                  value:
                    code: 400
                    error: if a ButtonReply is present, other button types are not allowed
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "403":
          description: "Forbidden: this route requires an Enterprise license. The server returns the fixed Portuguese message shown in the example."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 403
                error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error uploading media:` followed by the WhatsApp media upload error
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                header_upload_failed:
                  summary: Header upload failed
                  value:
                    code: 500
                    error: "error uploading media: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false

  /chat/send/flow:
    post:
      tags:
        - Send
      summary: Send native flow interactive message
      description: |
        Sends a WhatsApp Flow (an interactive form) to a contact.

        **How to use:**
        - Create the form in the WhatsApp Business app (Tools → Forms)
        - Send that form once from the app to any chat; the message reaches your webhook, where you copy `flow_id` (and the rest of `button_params_json`) and save it
        - Send the form through this endpoint with button `name` `galaxy_message` and the saved `flow_id` inside `button_params_json`
        - Submitted answers arrive at your configured webhook

        **Rules:**
        - Requires an Enterprise license (`403`)
        - Replace `flow_id` in the examples with your own form's ID
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FlowMessage"
            examples:
              contact_details_form:
                summary: Customer info form
                description: Asks the contact for name, email, phone and document. Replace flow_id with your form's id.
                value:
                  phone: "5511999999999"
                  body: Please send us your details.
                  message_version: 3
                  buttons:
                    - name: galaxy_message
                      button_params_json: '{"flow_message_version":"4","flow_id":"1380039803972405","flow_action_payload":{"screen":"contact_details","data":{"full_name_visible":true,"phone_number_visible":true,"email_visible":true,"cpf_or_cnpj_visible":true,"delivery_address_visible":false,"citizenship_card_visible":false,"custom_fields":[{"type":"TEXT_INPUT","label":"Delivery address"}]}},"well_version":"V700","flow_cta":"__localize:FLOWS_COMPLETE_FORM_BUTTON_TITLE","flow_action":"navigate","flow_token":"Q1VTVE9NRVJfSU5GTw==","form_type":"template"}'
              offer_signup_form:
                summary: Offer sign-up form
                description: Sign-up form with an offer title and description. Replace flow_id with your form's id.
                value:
                  phone: "5511999999999"
                  body: Claim your offer — fill out the form.
                  message_version: 3
                  buttons:
                    - name: galaxy_message
                      button_params_json: '{"flow_message_version":"4","flow_id":"2804269256615624","flow_action_payload":{"screen":"contact_details","data":{"full_name_visible":true,"phone_number_visible":true,"email_visible":true,"delivery_address_visible":false,"cpf_or_cnpj_visible":false,"citizenship_card_visible":false,"offer_name":"Special offer","offer_description":"offer description"}},"well_version":"V700","flow_cta":"__localize:FLOWS_COMPLETE_FORM_BUTTON_TITLE","flow_action":"navigate","flow_token":"T0ZGRVJfU0lHTlVQ","form_type":"template"}'
      responses:
        "200":
          description: Flow message sent successfully
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26A1D6B8A9F
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing body text in Payload`
            - `missing Phone in Payload`
            - `at least one button is required`
            - `button name is required`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
            - `invalid button name "flow": it renders as "message not compatible" on the recipient. For a real WhatsApp flow form use name "galaxy_message" with buttonParamsJson form_type "template". See this endpoint's description for a complete, working example.`
            - `invalid button name "send form": native_flow button names are snake_case without spaces (e.g. "galaxy_message")`
            - `button params required for 'galaxy_message'`
            - `invalid JSON in button params for button 'galaxy_message'`
            - `invalid JSON in message_params`
            - `error processing header media:` followed by the header media download or decoding error
            - `unsupported header media type:` followed by the unsupported `Header.Media.Type`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_body_text_in_payload:
                  summary: missing body text in Payload
                  value:
                    code: 400
                    error: missing body text in Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                at_least_one_button_is_required:
                  summary: at least one button is required
                  value:
                    code: 400
                    error: at least one button is required
                    success: false
                button_name_is_required:
                  summary: button name is required
                  value:
                    code: 400
                    error: button name is required
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
                button_named_flow:
                  summary: Button named flow
                  value:
                    code: 400
                    error: "invalid button name \"flow\": it renders as \"message not compatible\" on the recipient. For a real WhatsApp flow form use name \"galaxy_message\" with buttonParamsJson form_type \"template\". See this endpoint's description for a complete, working example."
                    success: false
                button_name_with_spaces:
                  summary: Button name with spaces
                  value:
                    code: 400
                    error: 'invalid button name "send form": native_flow button names are snake_case without spaces (e.g. "galaxy_message")'
                    success: false
                button_without_params:
                  summary: Button without params
                  value:
                    code: 400
                    error: button params required for 'galaxy_message'
                    success: false
                invalid_button_params_json:
                  summary: Invalid button params JSON
                  value:
                    code: 400
                    error: invalid JSON in button params for button 'galaxy_message'
                    success: false
                invalid_message_params_json:
                  summary: Invalid message_params JSON
                  value:
                    code: 400
                    error: invalid JSON in message_params
                    success: false
                header_media_cannot_be_processed:
                  summary: Header media cannot be processed
                  value:
                    code: 400
                    error: "error processing header media: HTTP error 404 when downloading from URL"
                    success: false
                unsupported_header_media_type:
                  summary: Unsupported header media type
                  value:
                    code: 400
                    error: "unsupported header media type: audio"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "403":
          description: "Forbidden: this route requires an Enterprise license. The server returns the fixed Portuguese message shown in the example."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 403
                error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `error uploading header media:` followed by the WhatsApp media upload error
            - `Error sending message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                header_upload_failed:
                  summary: Header upload failed
                  value:
                    code: 500
                    error: "error uploading header media: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending message: server returned error 479"
                    success: false

  /chat/send/carousel:
    post:
      tags:
        - Send
      summary: Send carousel message with multiple cards
      description: |
        Send a carousel message containing multiple scrollable cards. Each card can include media (image, video, document),
        text content, and interactive buttons. Perfect for product showcases, service catalogs, and multi-option presentations.

        **Carousel Structure:**
        - **Phone**: Recipient phone number or group JID (required)
        - **Message**: Introductory message text before carousel (required)
        - **Carousel**: Array of card objects (required, at least 1 card)

        **Card Structure:**
        Each card can contain:
        - **Text**: Card body text (required)
        - **MediaUrl**: URL or base64 data URL for media (optional)
        - **MediaType**: Type of media - image, video, or document (required if MediaUrl provided)
        - **Filename**: Document filename (optional, for documents)
        - **Caption**: Media caption text (optional)
        - **Buttons**: Array of button objects (required)

        **Button Types:**
        - 🔘 **reply** - Quick reply button (id + label)
        - 🔗 **url** - Opens web link (url + label)
        - 📋 **copy** - Copies code to clipboard (id as code + label)
        - 📞 **call** - Initiates phone call (id as phone + label)

        **Button Structure:**
        - **Id**: Button identifier (for reply/copy) or phone number (for call)
        - **Label**: Button display text
        - **Url**: Destination URL (for url type buttons)
        - **Type**: Button type (reply, url, copy, call)

        **Media Support:**
        - **Images**: JPEG, PNG, WebP (recommended max 5MB)
        - **Videos**: MP4, 3GP (recommended max 16MB, max 90 seconds)
        - **Documents**: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT

        **Common Use Cases:**
        - 🛍️ Product catalogs with images and buy buttons
        - 🏨 Hotel room options with photos and booking links
        - 🍕 Restaurant menu with dishes and order buttons
        - 🎓 Course offerings with descriptions and enrollment
        - 🏢 Service packages with pricing and contact options
        - 📱 Feature comparisons with specifications

        **Best Practices:**
        - Use 2-10 cards per carousel (optimal user experience)
        - Keep card text concise (50-100 characters recommended)
        - Use high-quality images (at least 800x600px)
        - Limit to 1-3 buttons per card
        - Maintain consistent card structure throughout carousel

        **Advanced Features:**
        - Context info for replying to messages
        - Mention support for tagging users
        - Forward info for message forwarding
        - Presence and duration control
        - Number validation before sending
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CarouselMessage"
            examples:
              product_catalog:
                summary: Product Catalog
                description: E-commerce product showcase with images
                value:
                  Phone: "5511999999999"
                  Message: Check out our featured products! 🛍️
                  Carousel:
                    - Text: |-
                        Premium Wireless Headphones 🎧

                        • Noise cancellation
                        • 30h battery
                        • Premium sound

                        $299.99
                      MediaUrl: https://picsum.photos/600/400.jpg
                      MediaType: image
                      Caption: Best Seller!
                      Buttons:
                        - Id: buy_headphones
                          Label: 🛒 Buy Now
                          Url: https://store.example.com/headphones
                          Type: url
                        - Id: headphones_details
                          Label: ℹ️ Details
                          Type: reply
                    - Text: |-
                        Smart Watch Pro ⌚

                        • Fitness tracking
                        • Heart rate monitor
                        • Water resistant

                        $199.99
                      MediaUrl: https://picsum.photos/600/400.jpg
                      MediaType: image
                      Caption: New Arrival!
                      Buttons:
                        - Id: buy_watch
                          Label: 🛒 Buy Now
                          Url: https://store.example.com/watch
                          Type: url
                        - Id: watch_details
                          Label: ℹ️ Details
                          Type: reply
                    - Text: |-
                        Bluetooth Speaker 🔊

                        • 360° sound
                        • 12h battery
                        • Portable design

                        $79.99
                      MediaUrl: https://picsum.photos/600/400.jpg
                      MediaType: image
                      Caption: Hot Deal!
                      Buttons:
                        - Id: buy_speaker
                          Label: 🛒 Buy Now
                          Url: https://store.example.com/speaker
                          Type: url
                        - Id: speaker_details
                          Label: ℹ️ Details
                          Type: reply
              video_carousel:
                summary: Video Carousel
                description: Product demos with video previews
                value:
                  Phone: "5511999999999"
                  Message: Watch our product demonstrations! 🎥
                  Carousel:
                    - Text: |-
                        Product Setup Tutorial

                        Learn how to set up your new device in under 5 minutes
                      MediaUrl: https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
                      MediaType: video
                      Caption: Quick Setup Guide
                      Buttons:
                        - Id: PDF_MANUAL_123
                          Label: 📋 Copy Manual Code
                          Type: copy
                        - Id: full_tutorial
                          Label: 🎬 Full Tutorial
                          Url: https://youtube.com/watch?v=example
                          Type: url
                    - Text: |-
                        Advanced Features Demo

                        Discover pro tips and hidden features
                      MediaUrl: https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
                      MediaType: video
                      Caption: Pro Tips
                      Buttons:
                        - Id: features_guide
                          Label: 📖 Feature Guide
                          Type: reply
      responses:
        "200":
          description: Carousel message sent successfully
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        allOf:
                          - $ref: "#/components/schemas/SendMessageResult"
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      Details: Sent
                      Id: 3EB0C767D26A1D6B8A9F
                      Timestamp: "2022-04-20T12:49:08-03:00"
                    success: true
        "400":
          description: |
            The request was rejected before sending. Possible `error` values:
            - `could not read request body`
            - `could not decode Payload`
            - `missing Phone in Payload`
            - `missing Carousel in Payload`
            - `could not parse Phone`
            - `phone number is not registered on WhatsApp`
            - `missing Participant in ContextInfo`
            - `failed to check if user is on WhatsApp:` followed by the WhatsApp lookup error
            - `error processing media:` followed by the card media download or decoding error
            - `invalid media type`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_read_request_body:
                  summary: could not read request body
                  value:
                    code: 400
                    error: could not read request body
                    success: false
                could_not_decode_payload:
                  summary: could not decode Payload
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone_in_payload:
                  summary: missing Phone in Payload
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_carousel_in_payload:
                  summary: missing Carousel in Payload
                  value:
                    code: 400
                    error: missing Carousel in Payload
                    success: false
                could_not_parse_phone:
                  summary: could not parse Phone
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                number_check_failed:
                  summary: Number check failed
                  value:
                    code: 400
                    error: phone number is not registered on WhatsApp
                    success: false
                reply_without_participant:
                  summary: Reply without participant
                  value:
                    code: 400
                    error: missing Participant in ContextInfo
                    success: false
                number_check_lookup_failed:
                  summary: Number check lookup failed
                  value:
                    code: 400
                    error: "failed to check if user is on WhatsApp: websocket not connected"
                    success: false
                card_media_cannot_be_processed:
                  summary: Card media cannot be processed
                  value:
                    code: 400
                    error: "error processing media: HTTP error 404 when downloading from URL"
                    success: false
                unknown_card_media_type:
                  summary: Unknown card media type
                  value:
                    code: 400
                    error: invalid media type
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "403":
          description: "Forbidden: this route requires an Enterprise license. The server returns the fixed Portuguese message shown in the example."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 403
                error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                success: false
        "500":
          description: |
            The session is missing, or preparing or sending the message failed. Possible `error` values:
            - `no session`
            - `failed to upload file:` followed by the WhatsApp media upload error
            - `Error sending carousel message:` followed by the WhatsApp send error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: no session
                  value:
                    code: 500
                    error: no session
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: "failed to upload file: failed to refresh media connections: websocket not connected"
                    success: false
                whatsapp_rejected_the_send:
                  summary: WhatsApp rejected the send
                  value:
                    code: 500
                    error: "Error sending carousel message: server returned error 479"
                    success: false
  /chat/downloadimage:
    post:
      tags:
        - Chat
      summary: Download the image of a message
      description: |
        Downloads and decrypts the image of a received or sent message and returns the whole file inline as a base64 data URL, ready for an HTML `<img>` tag or to be saved to disk.

        **Where the fields come from:** Copy the fields from `event.Message.imageMessage` of the `Message` webhook event (view-once images are nested under `viewOnceMessageV2.message.imageMessage`). Keys are matched case-insensitively and unknown keys are ignored, so that `imageMessage` object can be posted unchanged.

        **Rules (image):**
        - `DirectPath`, `MediaKey`, `FileEncSHA256` and `FileSHA256` are what the image download needs; without `DirectPath` it fails with **500** `failed to download image no url present`.
        - The image `MediaKey` and both hashes are standard base64 strings exactly as in the webhook JSON; a value that is not valid base64 returns **400** `could not decode Payload`.
        - A key or hash that does not match the image fails with **500** `failed to download image invalid media hmac` (or a hash mismatch detail); an expired CDN link fails with `download failed with status code 404` or `410`.
        - `Url` and `FileLength` are accepted but not used for the image. `Mimetype` is not validated: it is echoed in the response and used as the image data URL media type.
        - JPEG is the usual format; `Data` starts with `data:image/jpeg;base64,`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDownloadMediaRequest"
            examples:
              image_fields:
                summary: Fields copied from the webhook imageMessage
                description: Minimal body with the gateway field names.
                value:
                  Url: "https://mmg.whatsapp.net/o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c"
                  DirectPath: "/o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c"
                  MediaKey: "aQGcesqhANsFce2gBbNeXnE4LU9wA6LM5lA1ztbBduM="
                  Mimetype: "image/jpeg"
                  FileEncSHA256: "8wdTCF7kBugCQhneOg2YLsDye4Eqwu7N8kzyXKJXoIU="
                  FileSHA256: "Gc6NIzcYSI596Ke0Y0sO1VstRgq8FpAUzOglP06aYyE="
                  FileLength: 48213
              webhook_object_verbatim:
                summary: Posting event.Message.imageMessage unchanged
                description: Webhook keys (`URL`, `directPath`, `mediaKey`, ...) map case-insensitively to the body fields; extra keys are ignored.
                value:
                  URL: "https://mmg.whatsapp.net/o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c"
                  directPath: "/o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c"
                  mediaKey: "aQGcesqhANsFce2gBbNeXnE4LU9wA6LM5lA1ztbBduM="
                  mimetype: "image/jpeg"
                  fileEncSHA256: "8wdTCF7kBugCQhneOg2YLsDye4Eqwu7N8kzyXKJXoIU="
                  fileSHA256: "Gc6NIzcYSI596Ke0Y0sO1VstRgq8FpAUzOglP06aYyE="
                  fileLength: 48213
                  caption: "Order photo"
                  height: 1280
                  width: 960
                  mediaKeyTimestamp: 1757862000
      responses:
        "200":
          description: The image was downloaded and decrypted; `data.Data` holds the whole file as a data URL (truncated in this example).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDownloadMediaResponse"
              example:
                code: 200
                data:
                  Mimetype: "image/jpeg"
                  Data: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIy"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `MediaKey`/`FileEncSHA256`/`FileSHA256` is not valid base64."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the per-user files directory could not be created, or the image download/decryption failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                no_url_present:
                  summary: DirectPath missing
                  value:
                    code: 500
                    error: failed to download image no url present
                    success: false
                invalid_media_hmac:
                  summary: MediaKey does not match the file
                  value:
                    code: 500
                    error: failed to download image invalid media hmac
                    success: false
                expired_link:
                  summary: CDN link expired
                  value:
                    code: 500
                    error: failed to download image download failed with status code 410
                    success: false
                user_directory:
                  summary: Files directory could not be created
                  value:
                    code: 500
                    error: could not create user directory (/opt/zuckzapgo/files/user_3f2a9c1d)
                    success: false
  /chat/downloadvideo:
    post:
      tags:
        - Chat
      summary: Download the video of a message
      description: |
        Downloads and decrypts the video of a received or sent message and returns the whole file inline as a base64 data URL, ready for an HTML `<video>` tag or to be saved to disk.

        **Where the fields come from:** Copy the fields from `event.Message.videoMessage` of the `Message` webhook event (round video notes use the same fields under `ptvMessage`). Keys are matched case-insensitively and unknown keys are ignored, so that `videoMessage` object can be posted unchanged.

        **Rules (video):**
        - `DirectPath`, `MediaKey`, `FileEncSHA256` and `FileSHA256` are what the video download needs; without `DirectPath` it fails with **500** `failed to download video no url present`.
        - The video `MediaKey` and both hashes are standard base64 strings exactly as in the webhook JSON; a value that is not valid base64 returns **400** `could not decode Payload`.
        - A key or hash that does not match the video fails with **500** `failed to download video invalid media hmac` (or a hash mismatch detail); an expired CDN link fails with `download failed with status code 404` or `410`.
        - `Url` and `FileLength` are accepted but not used for the video. `Mimetype` is not validated: it is echoed in the response and used as the video data URL media type.
        - The base64 body is about 33% larger than the video itself; prefer S3 media delivery for large videos.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDownloadMediaRequest"
            examples:
              video_fields:
                summary: Fields copied from the webhook videoMessage
                description: Minimal body with the gateway field names.
                value:
                  Url: "https://mmg.whatsapp.net/v/t62.7161-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  DirectPath: "/v/t62.7161-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  MediaKey: "TzA1SP0MVnA9QdoPO44ev2R8UVuiAdV+swlmjv3vNMQ="
                  Mimetype: "video/mp4"
                  FileEncSHA256: "aOLrc4v0IpCOrMrZdkJnbVxl2mjiJ2OiaXbVDHwPja4="
                  FileSHA256: "qUYqZxL/XODxflh6YPYvdOOLLor5G6E5BkRK+fgSLLY="
                  FileLength: 1843200
              webhook_object_verbatim:
                summary: Posting event.Message.videoMessage unchanged
                description: Webhook keys (`URL`, `directPath`, `mediaKey`, ...) map case-insensitively to the body fields; extra keys are ignored.
                value:
                  URL: "https://mmg.whatsapp.net/v/t62.7161-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  directPath: "/v/t62.7161-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  mediaKey: "TzA1SP0MVnA9QdoPO44ev2R8UVuiAdV+swlmjv3vNMQ="
                  mimetype: "video/mp4"
                  fileEncSHA256: "aOLrc4v0IpCOrMrZdkJnbVxl2mjiJ2OiaXbVDHwPja4="
                  fileSHA256: "qUYqZxL/XODxflh6YPYvdOOLLor5G6E5BkRK+fgSLLY="
                  fileLength: 1843200
                  seconds: 12
                  caption: "Unboxing"
      responses:
        "200":
          description: The video was downloaded and decrypted; `data.Data` holds the whole file as a data URL (truncated in this example).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDownloadMediaResponse"
              example:
                code: 200
                data:
                  Mimetype: "video/mp4"
                  Data: "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `MediaKey`/`FileEncSHA256`/`FileSHA256` is not valid base64."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the per-user files directory could not be created, or the video download/decryption failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                no_url_present:
                  summary: DirectPath missing
                  value:
                    code: 500
                    error: failed to download video no url present
                    success: false
                invalid_media_hmac:
                  summary: MediaKey does not match the file
                  value:
                    code: 500
                    error: failed to download video invalid media hmac
                    success: false
                expired_link:
                  summary: CDN link expired
                  value:
                    code: 500
                    error: failed to download video download failed with status code 410
                    success: false
                user_directory:
                  summary: Files directory could not be created
                  value:
                    code: 500
                    error: could not create user directory (/opt/zuckzapgo/files/user_3f2a9c1d)
                    success: false
  /chat/downloaddocument:
    post:
      tags:
        - Chat
      summary: Download the document of a message
      description: |
        Downloads and decrypts the document of a received or sent message (PDF, spreadsheet, archive or any other file) and returns the whole file inline as a base64 data URL.

        **Where the fields come from:** Copy the fields from `event.Message.documentMessage` of the `Message` webhook event, or from `documentWithCaptionMessage.message.documentMessage` when the document has a caption. Keys are matched case-insensitively and unknown keys are ignored, so that `documentMessage` object can be posted unchanged.

        **Rules (document):**
        - `DirectPath`, `MediaKey`, `FileEncSHA256` and `FileSHA256` are what the document download needs; without `DirectPath` it fails with **500** `failed to download document no url present`.
        - The document `MediaKey` and both hashes are standard base64 strings exactly as in the webhook JSON; a value that is not valid base64 returns **400** `could not decode Payload`.
        - A key or hash that does not match the document fails with **500** `failed to download document invalid media hmac` (or a hash mismatch detail); an expired CDN link fails with `download failed with status code 404` or `410`.
        - `Url` and `FileLength` are accepted but not used for the document. `Mimetype` is not validated: it is echoed in the response and used as the document data URL media type.
        - The original file name is not returned; keep `fileName` from the webhook event.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDownloadMediaRequest"
            examples:
              document_fields:
                summary: Fields copied from the webhook documentMessage
                description: Minimal body with the gateway field names.
                value:
                  Url: "https://mmg.whatsapp.net/v/t62.7119-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  DirectPath: "/v/t62.7119-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  MediaKey: "GKgwzoHzV4RrbwisIF1GdFXTt9RbCHM9vt/5gZ3P8vw="
                  Mimetype: "application/pdf"
                  FileEncSHA256: "t2yh2iPMW1nkv0t2bf0sP7EH9szzZtpfyzVtfgJ+qrg="
                  FileSHA256: "E+J9XK+0syXqLfYT9o/C8VgBtytAfz/vC5GFVn1s9dE="
                  FileLength: 254871
              webhook_object_verbatim:
                summary: Posting event.Message.documentMessage unchanged
                description: Webhook keys (`URL`, `directPath`, `mediaKey`, ...) map case-insensitively to the body fields; extra keys are ignored.
                value:
                  URL: "https://mmg.whatsapp.net/v/t62.7119-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  directPath: "/v/t62.7119-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  mediaKey: "GKgwzoHzV4RrbwisIF1GdFXTt9RbCHM9vt/5gZ3P8vw="
                  mimetype: "application/pdf"
                  fileEncSHA256: "t2yh2iPMW1nkv0t2bf0sP7EH9szzZtpfyzVtfgJ+qrg="
                  fileSHA256: "E+J9XK+0syXqLfYT9o/C8VgBtytAfz/vC5GFVn1s9dE="
                  fileLength: 254871
                  title: "invoice-2026-09"
                  pageCount: 2
                  fileName: "invoice-2026-09.pdf"
      responses:
        "200":
          description: The document was downloaded and decrypted; `data.Data` holds the whole file as a data URL (truncated in this example).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDownloadMediaResponse"
              example:
                code: 200
                data:
                  Mimetype: "application/pdf"
                  Data: "data:application/pdf;base64,JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2Jq"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `MediaKey`/`FileEncSHA256`/`FileSHA256` is not valid base64."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the per-user files directory could not be created, or the document download/decryption failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                no_url_present:
                  summary: DirectPath missing
                  value:
                    code: 500
                    error: failed to download document no url present
                    success: false
                invalid_media_hmac:
                  summary: MediaKey does not match the file
                  value:
                    code: 500
                    error: failed to download document invalid media hmac
                    success: false
                expired_link:
                  summary: CDN link expired
                  value:
                    code: 500
                    error: failed to download document download failed with status code 410
                    success: false
                user_directory:
                  summary: Files directory could not be created
                  value:
                    code: 500
                    error: could not create user directory (/opt/zuckzapgo/files/user_3f2a9c1d)
                    success: false
  /chat/downloadaudio:
    post:
      tags:
        - Chat
      summary: Download the audio of a message
      description: |
        Downloads and decrypts the audio of a received or sent message (audio file or PTT voice note) and returns the whole file inline as a base64 data URL, ready for an HTML `<audio>` tag or to be saved to disk.

        **Where the fields come from:** Copy the fields from `event.Message.audioMessage` of the `Message` webhook event; voice notes carry `PTT: true` there. Keys are matched case-insensitively and unknown keys are ignored, so that `audioMessage` object can be posted unchanged.

        **Rules (audio):**
        - `DirectPath`, `MediaKey`, `FileEncSHA256` and `FileSHA256` are what the audio download needs; without `DirectPath` it fails with **500** `failed to download audio no url present`.
        - The audio `MediaKey` and both hashes are standard base64 strings exactly as in the webhook JSON; a value that is not valid base64 returns **400** `could not decode Payload`.
        - A key or hash that does not match the audio fails with **500** `failed to download audio invalid media hmac` (or a hash mismatch detail); an expired CDN link fails with `download failed with status code 404` or `410`.
        - `Url` and `FileLength` are accepted but not used for the audio. `Mimetype` is not validated: it is echoed in the response and used as the audio data URL media type.
        - Voice notes are Ogg/Opus, so `Mimetype` is usually `audio/ogg; codecs=opus`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDownloadMediaRequest"
            examples:
              audio_fields:
                summary: Fields copied from the webhook audioMessage
                description: Minimal body with the gateway field names.
                value:
                  Url: "https://mmg.whatsapp.net/v/t62.7117-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  DirectPath: "/v/t62.7117-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  MediaKey: "uYP/0reexMYRve9yzhNYWm1Yg80HpiSawTMxht4AR/8="
                  Mimetype: "audio/ogg; codecs=opus"
                  FileEncSHA256: "BRZJpqMSy97nxJJBjBgQEqwQWEU345ai++oAw1Kgv/E="
                  FileSHA256: "ndJ/Uki+LYYxZGn1hgjJATgFIIqT1SUlLQogFvta1mM="
                  FileLength: 18342
              webhook_object_verbatim:
                summary: Posting event.Message.audioMessage unchanged
                description: Webhook keys (`URL`, `directPath`, `mediaKey`, ...) map case-insensitively to the body fields; extra keys are ignored.
                value:
                  URL: "https://mmg.whatsapp.net/v/t62.7117-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  directPath: "/v/t62.7117-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  mediaKey: "uYP/0reexMYRve9yzhNYWm1Yg80HpiSawTMxht4AR/8="
                  mimetype: "audio/ogg; codecs=opus"
                  fileEncSHA256: "BRZJpqMSy97nxJJBjBgQEqwQWEU345ai++oAw1Kgv/E="
                  fileSHA256: "ndJ/Uki+LYYxZGn1hgjJATgFIIqT1SUlLQogFvta1mM="
                  fileLength: 18342
                  seconds: 7
                  PTT: true
      responses:
        "200":
          description: The audio was downloaded and decrypted; `data.Data` holds the whole file as a data URL (truncated in this example).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDownloadMediaResponse"
              example:
                code: 200
                data:
                  Mimetype: "audio/ogg; codecs=opus"
                  Data: "data:audio/ogg; codecs=opus;base64,T2dnUwACAAAAAAAAAAAAAAAAAAAAAMhHv8oBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dT"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `MediaKey`/`FileEncSHA256`/`FileSHA256` is not valid base64."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the per-user files directory could not be created, or the audio download/decryption failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                no_url_present:
                  summary: DirectPath missing
                  value:
                    code: 500
                    error: failed to download audio no url present
                    success: false
                invalid_media_hmac:
                  summary: MediaKey does not match the file
                  value:
                    code: 500
                    error: failed to download audio invalid media hmac
                    success: false
                expired_link:
                  summary: CDN link expired
                  value:
                    code: 500
                    error: failed to download audio download failed with status code 410
                    success: false
                user_directory:
                  summary: Files directory could not be created
                  value:
                    code: 500
                    error: could not create user directory (/opt/zuckzapgo/files/user_3f2a9c1d)
                    success: false
  /chat/downloadsticker:
    post:
      tags:
        - Chat
      summary: Download the sticker of a message
      description: |
        Downloads and decrypts the sticker of a received or sent message (static or animated WebP) and returns the whole file inline as a base64 data URL.

        **Where the fields come from:** Copy the fields from `event.Message.stickerMessage` of the `Message` webhook event. Keys are matched case-insensitively and unknown keys are ignored, so that `stickerMessage` object can be posted unchanged.

        **Rules (sticker):**
        - `DirectPath`, `MediaKey`, `FileEncSHA256` and `FileSHA256` are what the sticker download needs; without `DirectPath` it fails with **500** `failed to download sticker no url present`.
        - The sticker `MediaKey` and both hashes are standard base64 strings exactly as in the webhook JSON; a value that is not valid base64 returns **400** `could not decode Payload`.
        - A key or hash that does not match the sticker fails with **500** `failed to download sticker invalid media hmac` (or a hash mismatch detail); an expired CDN link fails with `download failed with status code 404` or `410`.
        - `Url` and `FileLength` are accepted but not used for the sticker. `Mimetype` is not validated: it is echoed in the response and used as the sticker data URL media type.
        - Items from `GET /business/sticker-pack/{packId}` expose `media_key`, `file_hash` and `file_enc_hash` in hex: convert them to base64 first, otherwise the download fails with `invalid media hmac`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDownloadMediaRequest"
            examples:
              sticker_fields:
                summary: Fields copied from the webhook stickerMessage
                description: Minimal body with the gateway field names.
                value:
                  Url: "https://mmg.whatsapp.net/v/t62.15575-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  DirectPath: "/v/t62.15575-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  MediaKey: "ARQlqjgLupJ1vQyWDC5cTv5GKrcuduMvjXJDRgrm2QI="
                  Mimetype: "image/webp"
                  FileEncSHA256: "THZUjSUStp6pDg8O0OiyCvkufOzScdfzxnucpeuxlMs="
                  FileSHA256: "CWAdQIgyvMC9mhYAgjQwbChLpbLJ7wPfnSFTiYJo+Qk="
                  FileLength: 21456
              webhook_object_verbatim:
                summary: Posting event.Message.stickerMessage unchanged
                description: Webhook keys (`URL`, `directPath`, `mediaKey`, ...) map case-insensitively to the body fields; extra keys are ignored.
                value:
                  URL: "https://mmg.whatsapp.net/v/t62.15575-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  directPath: "/v/t62.15575-24/11111111_2222222222222222_3333333333333333333_n.enc?ccb=11-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=5e03e0"
                  mediaKey: "ARQlqjgLupJ1vQyWDC5cTv5GKrcuduMvjXJDRgrm2QI="
                  mimetype: "image/webp"
                  fileEncSHA256: "THZUjSUStp6pDg8O0OiyCvkufOzScdfzxnucpeuxlMs="
                  fileSHA256: "CWAdQIgyvMC9mhYAgjQwbChLpbLJ7wPfnSFTiYJo+Qk="
                  fileLength: 21456
                  height: 512
                  width: 512
                  isAnimated: false
      responses:
        "200":
          description: The sticker was downloaded and decrypted; `data.Data` holds the whole file as a data URL (truncated in this example).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDownloadMediaResponse"
              example:
                code: 200
                data:
                  Mimetype: "image/webp"
                  Data: "data:image/webp;base64,UklGRroBAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `MediaKey`/`FileEncSHA256`/`FileSHA256` is not valid base64."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode Payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the per-user files directory could not be created, or the sticker download/decryption failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                no_url_present:
                  summary: DirectPath missing
                  value:
                    code: 500
                    error: failed to download sticker no url present
                    success: false
                invalid_media_hmac:
                  summary: MediaKey does not match the file
                  value:
                    code: 500
                    error: failed to download sticker invalid media hmac
                    success: false
                expired_link:
                  summary: CDN link expired
                  value:
                    code: 500
                    error: failed to download sticker download failed with status code 410
                    success: false
                user_directory:
                  summary: Files directory could not be created
                  value:
                    code: 500
                    error: could not create user directory (/opt/zuckzapgo/files/user_3f2a9c1d)
                    success: false
  /chat/presence:
    post:
      tags:
        - Chat
      summary: Show or clear the typing indicator in a chat
      description: |
        Sends a chat state to a contact or group so they see "typing…" or "recording audio…", or clears it.

        **Rules:**
        - `Phone` and `State` are required. `Phone` accepts a phone number, a user JID or a group JID.
        - `State` is `composing` (indicator on) or `paused` (indicator off). The value is sent as given without validation.
        - `Media: "audio"` turns `composing` into "recording audio…"; it is ignored for `paused`.
        - WhatsApp clears the indicator by itself after a few seconds, so repeat `composing` during long operations and send `paused` when done.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatPresenceStateRequest"
            examples:
              typing:
                summary: Show typing in a direct chat
                description: The contact sees "typing…" for a few seconds.
                value:
                  Phone: "5511999999999"
                  State: composing
              recording_audio:
                summary: Show recording audio
                description: "`Media: audio` shows the microphone indicator instead of typing."
                value:
                  Phone: "5511999999999"
                  State: composing
                  Media: audio
              stop_in_group:
                summary: Clear the indicator in a group
                description: "`paused` removes the indicator immediately."
                value:
                  Phone: "120363000000000011@g.us"
                  State: paused
      responses:
        "200":
          description: The chat state was sent to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatPresenceStateResponse"
              example:
                code: 200
                data:
                  Details: Chat presence set successfuly
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `Phone` or `State` is missing, or `Phone` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_phone:
                  summary: Phone missing
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                missing_state:
                  summary: State missing
                  value:
                    code: 400
                    error: missing State in Payload
                    success: false
                invalid_phone:
                  summary: Phone cannot be parsed
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the chat state could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Chat state could not be sent
                  value:
                    code: 500
                    error: failure sending chat presence to Whatsapp servers
                    success: false
  /chat/pin:
    post:
      tags:
        - Chat
      summary: Pin or unpin a chat in the chat list
      description: |
        Pins a chat (contact or group) to the top of the chat list on every linked device, or unpins it, by sending a
        `pin` app-state mutation. To pin a single message inside a conversation use `POST /chat/pin-message`.

        **Rules:**
        - `phone` is required; `pin` defaults to `false`, so omitting it unpins the chat.
        - `phone` accepts a phone number, a user JID (`@s.whatsapp.net` / `@lid`) or a group JID (`@g.us`) and is written to the mutation as given.
        - The official apps show at most 3 pinned chats; the gateway does not enforce this limit.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatPinRequest"
            examples:
              pin_contact:
                summary: Pin a contact chat
                description: Moves the direct chat to the top of the chat list.
                value:
                  phone: "5511999999999"
                  pin: true
              pin_group:
                summary: Pin a group chat
                description: Group chats are addressed by their `@g.us` JID.
                value:
                  phone: "120363000000000011@g.us"
                  pin: true
              unpin:
                summary: Unpin a chat
                description: "`pin: false` (or omitting `pin`) removes the chat from the pinned section."
                value:
                  phone: "5511999999999"
                  pin: false
      responses:
        "200":
          description: The `pin` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatPinResponse"
              examples:
                pinned:
                  summary: Chat pinned
                  value:
                    code: 200
                    data:
                      success: true
                      message: Chat pinned successfully
                      jid: 5511999999999@s.whatsapp.net
                      pinned: true
                    success: true
                unpinned:
                  summary: Chat unpinned
                  value:
                    code: 200
                    data:
                      success: true
                      message: Chat unpinned successfully
                      jid: 5511999999999@s.whatsapp.net
                      pinned: false
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `phone` is missing, or `phone` is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone:
                  summary: phone missing
                  value:
                    code: 400
                    error: missing phone in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `pin` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to update pin status
                    success: false
  /chat/archive:
    post:
      tags:
        - Chat
      summary: Archive or unarchive a chat
      description: |
        Moves a chat to the **Archived** folder on every linked device, or brings it back, by sending an `archive`
        app-state mutation. The official apps may unarchive a chat again when a new message arrives, depending on the user's settings.

        **Rules:**
        - `phone` is required; `archive` defaults to `false`, so omitting it unarchives the chat.
        - `last_message_timestamp` (Unix seconds) and `last_message_id` describe the most recent message the archive applies to. Without a timestamp the current time is used; without `last_message_id` no message key is sent.
        - `last_message_remote_jid` overrides the chat JID inside the message key (only used together with `last_message_id`).
        - `phone` is written to the mutation as given (no LID translation).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatArchiveRequest"
            examples:
              archive:
                summary: Archive a chat
                description: Minimal request; the archive is stamped with the current time.
                value:
                  phone: "5511999999999"
                  archive: true
              archive_with_last_message:
                summary: Archive up to a known last message
                description: Sends the last message key so other devices anchor the archive to that message.
                value:
                  phone: "5511999999999"
                  archive: true
                  last_message_timestamp: 1757862000
                  last_message_id: 3EB0C767D26A1D6B8A9F
                  last_message_from_me: false
              unarchive:
                summary: Unarchive a group
                description: "`archive: false` moves the chat back to the main list."
                value:
                  phone: "120363000000000011@g.us"
                  archive: false
      responses:
        "200":
          description: The `archive` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatArchiveResponse"
              examples:
                archived:
                  summary: Chat archived
                  value:
                    code: 200
                    data:
                      success: true
                      message: Chat archived successfully
                      jid: 5511999999999@s.whatsapp.net
                      archived: true
                    success: true
                unarchived:
                  summary: Chat unarchived
                  value:
                    code: 200
                    data:
                      success: true
                      message: Chat unarchived successfully
                      jid: 120363000000000011@g.us
                      archived: false
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `phone` is missing, or `phone` is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone:
                  summary: phone missing
                  value:
                    code: 400
                    error: missing phone in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `archive` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to update archive status
                    success: false
  /chat/mute:
    post:
      tags:
        - Chat
      summary: Mute or unmute a chat
      description: |
        Silences notifications for a chat on every linked device, or restores them, by sending a `mute` app-state mutation.

        **Rules:**
        - `phone` is required; `mute` defaults to `false`, so omitting it unmutes the chat.
        - `mute_duration` accepts `8h`, `1w` or `always` (case-insensitive). Omitting it while muting means `always`. Any other value returns **400**.
        - `mute_duration` is ignored when unmuting.
        - `data.duration` echoes the raw `mute_duration` sent (empty string when it was omitted) and is only present when muting.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatMuteRequest"
            examples:
              mute_8h:
                summary: Mute for 8 hours
                description: Notifications come back automatically after 8 hours.
                value:
                  phone: "5511999999999"
                  mute: true
                  mute_duration: 8h
              mute_1w:
                summary: Mute a group for 1 week
                description: Notifications come back automatically after 7 days.
                value:
                  phone: "120363000000000011@g.us"
                  mute: true
                  mute_duration: 1w
              mute_always:
                summary: Mute until manually unmuted
                description: "`always` keeps the chat muted with no expiry."
                value:
                  phone: "5511999999999"
                  mute: true
                  mute_duration: always
              unmute:
                summary: Unmute a chat
                description: Restores notifications immediately.
                value:
                  phone: "5511999999999"
                  mute: false
      responses:
        "200":
          description: The `mute` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatMuteResponse"
              examples:
                muted:
                  summary: Chat muted for 8 hours
                  value:
                    code: 200
                    data:
                      success: true
                      message: Chat muted successfully
                      jid: 5511999999999@s.whatsapp.net
                      muted: true
                      duration: 8h
                    success: true
                unmuted:
                  summary: Chat unmuted
                  value:
                    code: 200
                    data:
                      success: true
                      message: Chat unmuted successfully
                      jid: 5511999999999@s.whatsapp.net
                      muted: false
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `phone` is missing or invalid, or `mute_duration` is not one of `8h`, `1w`, `always`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone:
                  summary: phone missing
                  value:
                    code: 400
                    error: missing phone in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
                invalid_duration:
                  summary: Unsupported mute_duration
                  value:
                    code: 400
                    error: "invalid mute duration. Use: 8h, 1w, or always"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `mute` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to update mute status
                    success: false
  /chat/star:
    post:
      tags:
        - Chat
      summary: Star or unstar a message
      description: |
        Adds a message to the **Starred messages** section of every linked device, or removes it, by sending a `star`
        app-state mutation built from the message key.

        **Rules:**
        - `chat_jid` and `message_id` are required; `star` defaults to `false`, so omitting it unstars the message.
        - `from_me` must match the real author of the message, otherwise the mutation does not match the message on the phone.
        - For a message written by another participant in a group, send `sender_jid` with the author.
        - People are addressed by LID when the mapping is known; `index_chat_jid` shows the chat JID written to the mutation.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatStarRequest"
            examples:
              own_message_dm:
                summary: Star a message this account sent
                description: Direct chat, message written by this account.
                value:
                  chat_jid: "5511999999999"
                  message_id: 3EB0C767D26A1D6B8A9F
                  from_me: true
                  star: true
              group_message_from_participant:
                summary: Star a group message from another member
                description: "`sender_jid` identifies the author of the group message."
                value:
                  chat_jid: "120363000000000011@g.us"
                  sender_jid: 5511888888888@s.whatsapp.net
                  message_id: 3EB0C767D26A1D6B8A9E
                  from_me: false
                  star: true
              unstar:
                summary: Unstar a received message
                description: "`star: false` removes the message from Starred messages."
                value:
                  chat_jid: 5511999999999@s.whatsapp.net
                  message_id: 3EB0C767D26A1D6B8A9D
                  from_me: false
                  star: false
      responses:
        "200":
          description: The `star` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatStarResponse"
              examples:
                starred:
                  summary: Message starred
                  value:
                    code: 200
                    data:
                      success: true
                      message: Message starred successfully
                      chat_jid: 5511999999999@s.whatsapp.net
                      message_id: 3EB0C767D26A1D6B8A9F
                      starred: true
                      index_chat_jid: 123456789012345@lid
                    success: true
                unstarred:
                  summary: Message unstarred
                  value:
                    code: 200
                    data:
                      success: true
                      message: Message unstarred successfully
                      chat_jid: 5511999999999@s.whatsapp.net
                      message_id: 3EB0C767D26A1D6B8A9D
                      starred: false
                      index_chat_jid: 123456789012345@lid
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `chat_jid` or `message_id` is missing, or a JID is invalid."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: chat_jid or message_id missing
                  value:
                    code: 400
                    error: missing chat_jid or message_id in payload
                    success: false
                invalid_chat_jid:
                  summary: chat_jid is not a valid number or JID
                  value:
                    code: 400
                    error: invalid chat JID
                    success: false
                invalid_sender_jid:
                  summary: sender_jid is not a valid number or JID
                  value:
                    code: 400
                    error: invalid sender JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `star` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to update star status
                    success: false
  /chat/delete-chat:
    post:
      tags:
        - Chat
      summary: Delete a chat from the chat list
      description: |
        Removes a whole conversation from the chat list of every linked device by sending a `deleteChat` app-state
        mutation. This is not the same as deleting a single message (`POST /chat/delete`); the chat reappears when a new message arrives.

        **Rules:**
        - `phone` is required and accepts a phone number, a user JID or a group JID.
        - `delete_media` defaults to `true` (media files of the chat are deleted too); send `false` to keep them.
        - `last_message_timestamp` (Unix seconds) anchors the deletion to the most recent message. `last_message_id` and `last_message_from_me` are only used together with the timestamp.
        - `last_message_sender_jid` is only used for group chats, to identify the author of the last message.
        - People are addressed by LID when this account knows the mapping (mutation index and the last message key `remoteJid`), like the official clients; groups keep their JID. The response `jid` is unchanged.
        - `last_system_message_timestamp` (optional, Unix seconds) marks the last system notice to delete, such as disappearing-messages notices. It defaults to `last_message_timestamp`, or to the current time when neither is sent.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatDeleteChatRequest"
            examples:
              minimal:
                summary: Delete a chat and its media
                description: Only `phone` is needed; media is deleted by default.
                value:
                  phone: "5511999999999"
              keep_media:
                summary: Delete a chat but keep its media
                description: "`delete_media: false` keeps the media files on the devices."
                value:
                  phone: "5511999999999"
                  delete_media: false
              group_with_last_message:
                summary: Delete a group chat up to its last message
                description: Sends the last message key, including its author, for a precise deletion.
                value:
                  phone: "120363000000000011@g.us"
                  last_message_timestamp: 1757862000
                  last_message_id: 3EB0C767D26A1D6B8A9F
                  last_message_from_me: false
                  last_message_sender_jid: 5511888888888@s.whatsapp.net
                  delete_media: true
      responses:
        "200":
          description: The `deleteChat` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatDeleteChatResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Chat deleted successfully
                  jid: 5511999999999@s.whatsapp.net
                  delete_media: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `phone` is missing, or `phone` is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phone:
                  summary: phone missing
                  value:
                    code: 400
                    error: missing phone in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `deleteChat` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                delete_failed:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to delete chat
                    success: false
  /chat/favorite:
    post:
      tags:
        - Chat
      summary: Add or remove a chat from Favorites
      description: |
        Adds one chat (contact or group) to the **Favorites** list of the official apps, or removes it.

        WhatsApp syncs Favorites as **one complete ordered list** (the `favorites` app-state action). To change a
        single entry the gateway applies the change on top of the list it last observed from app state or sent
        through this API (see `GET /chat/favorites`), then sends the whole list back.

        **Rules:**
        - `phone` and `favorite` are required; `phone` accepts a phone number, a user JID (`@s.whatsapp.net` / `@lid`) or a group JID (`@g.us`).
        - When the current list is unknown the request fails with **409** instead of silently wiping the other favorites.
        - `force: true` together with `favorite: true` replaces an unknown list with only this chat. `force` has no effect when removing.
        - Adding a chat that is already a favorite, or removing one that is not, still re-sends the current list.
        - People are addressed by LID when the mapping is known; `index_jid` shows the JID actually written to the sync action.

        **Use Cases:**
        - Mirror a CRM "starred customer" flag in the WhatsApp Favorites filter
        - Remove a closed conversation from Favorites
        - To set several chats at once, use `PUT /chat/favorites` with the complete list
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatFavoriteRequest"
            examples:
              add_contact:
                summary: Add a contact to Favorites
                description: Appends the contact to the end of the known Favorites list.
                value:
                  phone: "5511999999999"
                  favorite: true
              remove_contact:
                summary: Remove a contact from Favorites
                description: Removes the contact from the known Favorites list; the remaining order is preserved.
                value:
                  phone: "5511999999999"
                  favorite: false
              force_replace_group:
                summary: Unknown list, replace it with one group
                description: The gateway has not observed the Favorites list yet. `force` accepts replacing the whole list with only this group.
                value:
                  phone: "120363000000000011@g.us"
                  favorite: true
                  force: true
      responses:
        "200":
          description: Favorites list sent to WhatsApp; `data.favorites` is the complete list after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatFavoriteResponse"
              examples:
                added:
                  summary: Contact added
                  value:
                    code: 200
                    data:
                      success: true
                      jid: 5511999999999@s.whatsapp.net
                      index_jid: 123456789012345@lid
                      favorite: true
                      favorites:
                        - 120363000000000011@g.us
                        - 123456789012345@lid
                    success: true
                removed:
                  summary: Contact removed
                  value:
                    code: 200
                    data:
                      success: true
                      jid: 5511999999999@s.whatsapp.net
                      index_jid: 123456789012345@lid
                      favorite: false
                      favorites:
                        - 120363000000000011@g.us
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, a required field is missing, or `phone` is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: phone or favorite missing
                  value:
                    code: 400
                    error: missing phone or favorite in payload
                    success: false
                invalid_phone:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the gateway does not know the current Favorites list. Send the complete list with `PUT /chat/favorites`, or retry with `force: true` and `favorite: true`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 409
                error: current favorites list is unknown to this gateway; send the complete list with PUT /chat/favorites, or force=true with favorite=true to replace it with only this chat
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the app-state update."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: App-state update rejected
                  value:
                    code: 500
                    error: failed to update favorites
                    success: false
  /chat/favorites:
    get:
      tags:
        - Chat
      summary: Get the Favorites list
      description: |
        Returns the Favorites list known to the gateway for this user.

        **Behavior:**
        - **Without `refresh`:** returns the list last observed from app state or sent through this API. `known: false` means this process has not seen the list yet; `favorites` is then empty and does **not** mean the phone has no favorites.
        - **With `refresh=true`:** runs a full sync of the `regular_high` app-state collection and rebuilds the list from the decrypted snapshot. `complete: true` and `synced_at` are only returned after such a successful sync. The full sync does not emit favorites events to webhooks.

        **Rules:**
        - `source` is `app_state` (observed from the phone) or `api` (last sent through this API); it is empty while `known` is false.
        - The refresh errors (**501**, **502**, **503**) only happen with `refresh=true`. A failed sync returns **502**, never a partial list.
        - `refresh=true` is a heavy call; do not retry it automatically.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: refresh
          in: query
          required: false
          description: "`true` runs a full sync of the `regular_high` app-state collection before answering. Any value that is not a boolean is treated as `false`."
          schema:
            type: boolean
            default: false
          example: true
      responses:
        "200":
          description: Favorites list as currently known by the gateway.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatFavoritesListResponse"
              examples:
                refreshed:
                  summary: After refresh=true
                  value:
                    code: 200
                    data:
                      favorites:
                        - 123456789012345@lid
                        - 120363000000000011@g.us
                      known: true
                      complete: true
                      source: app_state
                      synced_at: "2026-09-15T01:40:00Z"
                    success: true
                unknown:
                  summary: List not observed yet
                  value:
                    code: 200
                    data:
                      favorites: []
                      known: false
                      complete: false
                      source: ""
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error (only with `refresh=true`): no WhatsApp session, or the session is not logged in."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_logged_in:
                  summary: Session exists but is not paired
                  value:
                    code: 500
                    error: not logged in
                    success: false
        "501":
          description: "Not Implemented (only with `refresh=true`): the bundled WhatsApp library cannot return a full app-state snapshot."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 501
                error: full app state snapshot is not supported by the bundled WhatsApp library
                success: false
        "502":
          description: "Bad Gateway (only with `refresh=true`): the full sync of `regular_high` failed. No partial list is returned."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "full sync of regular_high failed: mismatching LTHash"
                success: false
        "503":
          description: "Service Unavailable (only with `refresh=true`): the WhatsApp client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: client not connected to WhatsApp
                success: false
    put:
      tags:
        - Chat
      summary: Replace the Favorites list
      description: |
        Replaces the **complete ordered** Favorites list with the given chats, mirroring the official sync action,
        which always carries the whole list.

        **Rules:**
        - `phones` is required. Chats missing from the array are removed from Favorites; an empty array clears Favorites.
        - Each entry accepts a phone number, a user JID (`@s.whatsapp.net` / `@lid`) or a group JID (`@g.us`). The first invalid entry aborts the request with **400** and nothing is sent.
        - People are stored by LID when the mapping is known, so `data.favorites` may differ from the input.
        - A successful call makes the list "known", enabling `POST /chat/favorite` for single-chat changes.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatFavoritesReplaceRequest"
            examples:
              replace_list:
                summary: Replace Favorites with an ordered list
                description: Phone numbers, `@s.whatsapp.net`, `@lid` and group JIDs are accepted in any mix; the order is kept.
                value:
                  phones:
                    - "5511999999999"
                    - "120363000000000011@g.us"
                    - "123456789012346@lid"
              clear:
                summary: Clear Favorites
                description: An empty array is valid and removes every favorite. Omitting `phones` returns 400.
                value:
                  phones: []
      responses:
        "200":
          description: Favorites list sent to WhatsApp; `data.favorites` is the list exactly as written to the sync action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatFavoritesReplaceResponse"
              example:
                code: 200
                data:
                  success: true
                  favorites:
                    - 123456789012345@lid
                    - 120363000000000011@g.us
                    - 123456789012346@lid
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `phones` is missing, or an entry is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_phones:
                  summary: phones missing
                  value:
                    code: 400
                    error: missing phones in payload
                    success: false
                invalid_entry:
                  summary: An entry is not a valid number or JID
                  value:
                    code: 400
                    error: "invalid phone number: @s.whatsapp.net"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the app-state update."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: App-state update rejected
                  value:
                    code: 500
                    error: failed to update favorites
                    success: false
  /chat/pin-message:
    post:
      tags:
        - Chat
      summary: Pin or unpin a message inside a chat
      description: |
        Sends the official `PinInChatMessage` (`PIN_FOR_ALL` / `UNPIN_FOR_ALL`) that shows the pinned-message banner at the
        top of the conversation for every participant. To pin the chat itself in the chat list use `POST /chat/pin`.

        **Rules:**
        - `phone`, `message_id`, `from_me` and `pin` are all required (`false` counts as present).
        - `duration_seconds` accepts `86400` (24 hours), `604800` (7 days, default) or `2592000` (30 days); any other value returns **400**. It is ignored when unpinning.
        - `from_me` tells whether the target message was sent by this account.
        - In a group, pinning another participant's message requires `sender_jid` (**400** otherwise). For your own message in a group the gateway fills in this account as the author.
        - People are addressed by LID when the mapping is known, so the embedded key matches the destination.
        - WhatsApp may restrict pinning to group admins depending on the group settings.
        - This sends a protocol message: do not retry automatically after an uncertain failure.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatPinMessageRequest"
            examples:
              pin_7_days:
                summary: Pin for 7 days (default)
                description: No `duration_seconds`, so the default 7-day pin is used.
                value:
                  phone: "5511999999999"
                  message_id: 3EB0C767D26A1D6B8A9F
                  from_me: true
                  pin: true
              pin_group_24h:
                summary: Pin another member's group message for 24 hours
                description: "`sender_jid` is required because the message was written by another participant."
                value:
                  phone: "120363000000000011@g.us"
                  message_id: 3EB0C767D26A1D6B8A9E
                  from_me: false
                  sender_jid: 5511888888888@s.whatsapp.net
                  pin: true
                  duration_seconds: 86400
              unpin:
                summary: Unpin a message
                description: "`pin: false` removes the pinned-message banner; `duration_seconds` is ignored."
                value:
                  phone: "5511999999999"
                  message_id: 3EB0C767D26A1D6B8A9F
                  from_me: true
                  pin: false
      responses:
        "200":
          description: The pin message was sent and acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatPinMessageResponse"
              examples:
                pinned:
                  summary: Message pinned for 7 days
                  value:
                    code: 200
                    data:
                      success: true
                      id: 3EB0B1C2D3E4F5A6B7C8
                      chat: 123456789012345@lid
                      message_id: 3EB0C767D26A1D6B8A9F
                      pinned: true
                      timestamp: 1757862000
                      duration_seconds: 604800
                    success: true
                unpinned:
                  summary: Message unpinned
                  value:
                    code: 200
                    data:
                      success: true
                      id: 3EB0B1C2D3E4F5A6B7C9
                      chat: 123456789012345@lid
                      message_id: 3EB0C767D26A1D6B8A9F
                      pinned: false
                      timestamp: 1757862060
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, a required field is missing, a JID is invalid, `duration_seconds` is not allowed, or `sender_jid` is missing for another member's group message."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: Required field missing
                  value:
                    code: 400
                    error: missing phone, message_id, from_me or pin in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
                invalid_duration:
                  summary: Unsupported duration_seconds
                  value:
                    code: 400
                    error: "invalid duration_seconds: must be 86400, 604800 or 2592000"
                    success: false
                sender_required:
                  summary: Group message from another member without sender_jid
                  value:
                    code: 400
                    error: sender_jid is required for another participant's message in a group
                    success: false
                invalid_sender_jid:
                  summary: sender_jid is not a valid number or JID
                  value:
                    code: 400
                    error: invalid sender JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the pin message could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: failed to send pin message
                    success: false

  /chat/list:
    get:
      tags:
        - Sync
      summary: List chats with pinned, archived and muted settings
      description: |
        Lists the chats whose settings (pinned, archived, muted) were already synchronized into the local database table `whatsmeow_chat_settings`. The call reads the database only; it does not contact WhatsApp.

        **Rules:**
        - Only chats that have at least one setting stored are returned, ordered pinned first, then non-archived. Read/unread state and names are not stored and are not returned.
        - `phone_number` is the user part of the JID (for `@lid` and `@g.us` chats it is the LID or group ID, not a phone number).
        - Run `POST /sync/app-state` first when the list looks incomplete.
        - When no WhatsApp client exists for this user the response is **404** in the legacy envelope: the message sits in `data` and `success` stays `true`, so check the HTTP status.
        - A session that was never paired returns **500** `not logged in`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Chats with stored settings and summary counters.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncChatListResponse"
              example:
                code: 200
                data:
                  success: true
                  chats:
                    - chat_jid: 5511999999999@s.whatsapp.net
                      phone_number: "5511999999999"
                      pinned: true
                      archived: false
                    - chat_jid: 120363000000000011@g.us
                      phone_number: "120363000000000011"
                      muted_until: 1767225600
                      pinned: false
                      archived: true
                  stats:
                    total: 2
                    pinned_count: 1
                    archived_count: 1
                    muted_count: 1
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found (legacy envelope): no WhatsApp client exists for this user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 404
                data: Client not found
                success: true
        "500":
          description: "Internal Server Error: the session was never paired (standard envelope), or the database query failed (legacy envelope)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                not_logged_in:
                  summary: Session not paired
                  value:
                    code: 500
                    error: not logged in
                    success: false
                database_query_failed:
                  summary: Database query failed (legacy envelope)
                  value:
                    code: 500
                    data: "Database query failed: pq: relation \"whatsmeow_chat_settings\" does not exist"
                    success: true

  /sync/app-state:
    post:
      tags:
        - Sync
      summary: Resynchronize all app-state collections
      description: |
        Fetches the five app-state collections (`critical_block`, `critical_unblock_low`, `regular_low`, `regular_high`, `regular`) from WhatsApp without reconnecting, updating chat settings, contacts, labels and other synced data in the local store.

        **Behavior:**
        - The collections are fetched one after another; the call always answers **200** once all five were attempted. `data.success` is `false` when any collection failed, and each item in `data.results` carries its own `success`, `error` and `duration`.
        - With `auto_recovery` (default `true`) a failed collection triggers an app-state recovery request to the primary phone; `recovery_requested` shows whether it was sent. Recovered data arrives later through app-state events.

        **Rules:**
        - Options come from a JSON body when one is sent; a body that does not parse is ignored and the defaults (`full_sync: true`, `auto_recovery: true`) are kept.
        - Without a body, the `full_sync` and `auto_recovery` query or form parameters are read; only the literal value `false` disables an option.
        - `full_sync: true` discards the local collection versions and downloads full snapshots; `false` only fetches patches newer than the stored versions.
        - Precondition failures use the legacy envelope (message in `data`, `success: true`): **404** when no client exists and **400** when it is not connected. A never-paired session returns **500** `not logged in`.
        - Full syncs can take several seconds; do not call this in a tight loop.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: full_sync
          in: query
          required: false
          description: Only read when the request has no body. `false` fetches incremental patches instead of full snapshots.
          schema:
            type: boolean
            default: true
          example: false
        - name: auto_recovery
          in: query
          required: false
          description: Only read when the request has no body. `false` disables the recovery request for failed collections.
          schema:
            type: boolean
            default: true
          example: true
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncAppStateRequest"
            examples:
              defaults:
                summary: Full resync with automatic recovery
                description: Same as sending no body at all.
                value:
                  full_sync: true
                  auto_recovery: true
              incremental_no_recovery:
                summary: Incremental fetch without recovery
                description: Fetches only newer patches and never asks the phone for a recovery snapshot.
                value:
                  full_sync: false
                  auto_recovery: false
      responses:
        "200":
          description: All five collections were attempted; inspect `data.success` and `data.results`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncAppStateResponse"
              examples:
                all_synced:
                  summary: Every collection synchronized
                  value:
                    code: 200
                    data:
                      success: true
                      message: App state synchronized successfully. All 5 patch types updated.
                      auto_recovery_enabled: true
                      results:
                        - name: critical_block
                          success: true
                          duration: 812.4ms
                        - name: critical_unblock_low
                          success: true
                          duration: 640.1ms
                        - name: regular_low
                          success: true
                          duration: 1.203s
                        - name: regular_high
                          success: true
                          duration: 955.7ms
                        - name: regular
                          success: true
                          duration: 1.412s
                    success: true
                partial_failure:
                  summary: One collection failed and recovery was requested
                  value:
                    code: 200
                    data:
                      success: false
                      message: App state sync completed with errors. Recovery requested for 1 failed patches. Check results for details.
                      auto_recovery_enabled: true
                      results:
                        - name: critical_block
                          success: true
                          duration: 790.2ms
                        - name: critical_unblock_low
                          success: true
                          duration: 610.8ms
                        - name: regular_low
                          success: true
                          duration: 1.101s
                        - name: regular_high
                          success: false
                          error: "failed to verify patch v1234: mismatching LTHash"
                          duration: 2.034s
                          recovery_requested: true
                        - name: regular
                          success: true
                          duration: 1.388s
                    success: true
        "400":
          description: "Bad Request (legacy envelope): the WhatsApp client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 400
                data: Client not connected to WhatsApp
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found (legacy envelope): no WhatsApp client exists for this user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 404
                data: Client not found
                success: true
        "500":
          description: "Internal Server Error: the session was never paired."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: not logged in
                success: false

  /sync/history-request:
    post:
      tags:
        - Sync
      summary: Request older messages of one chat from the phone
      description: |
        Asks the primary phone for messages of one chat that are older than a known message (on-demand history sync). The messages are **not** returned in the HTTP response; they arrive asynchronously as `HistorySync` events through the webhook, WebSocket and other transports.

        **Rules:**
        - `phone`, `oldest_message_id` and `oldest_message_timestamp` are required. `phone` accepts a phone number, a user JID or a group JID.
        - `oldest_message_timestamp` is the timestamp of that oldest known message in **milliseconds** since the Unix epoch.
        - `oldest_from_me` must match the author of that message. `count` defaults to 50; WhatsApp decides how many messages are really sent.
        - `data.request_id` is simply the `oldest_message_id`; the phone must be online for history to arrive.
        - Every validation and precondition failure uses the legacy envelope: the text is in `data` and `success` is `true`. Branch on the HTTP status (**400**, **404**, **500**).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncHistoryRequestRequest"
            examples:
              default_count:
                summary: Request the default 50 older messages
                description: "`count` omitted, so 50 messages are requested."
                value:
                  phone: "5511999999999"
                  oldest_message_id: 3EB0C767D26A1D6B8A9F
                  oldest_message_timestamp: 1757862000000
                  oldest_from_me: false
              group_page:
                summary: Page 100 older messages of a group
                description: Uses the oldest message already stored as the anchor for the next page.
                value:
                  phone: "120363000000000011@g.us"
                  oldest_message_id: 3EB0C767D26A1D6B8A9E
                  oldest_message_timestamp: 1757775600000
                  oldest_from_me: true
                  count: 100
      responses:
        "200":
          description: The history request was sent to the primary phone; results arrive later as `HistorySync` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncHistoryRequestResponse"
              example:
                code: 200
                data:
                  success: true
                  message: History sync request sent successfully. History will arrive asynchronously via HistorySync events (webhook/websocket).
                  request_id: 3EB0C767D26A1D6B8A9F
                  chat: 5511999999999@s.whatsapp.net
                  count: 50
                success: true
        "400":
          description: "Bad Request (legacy envelope): client not connected, body not valid JSON, a required field missing, or `phone` invalid."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 400
                    data: Client not connected to WhatsApp
                    success: true
                invalid_request_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    data: Invalid request body
                    success: true
                phone_is_required:
                  summary: phone missing
                  value:
                    code: 400
                    data: phone is required
                    success: true
                oldest_message_id_is_required:
                  summary: oldest_message_id missing
                  value:
                    code: 400
                    data: oldest_message_id is required
                    success: true
                oldest_message_timestamp_is_required:
                  summary: oldest_message_timestamp missing or zero
                  value:
                    code: 400
                    data: oldest_message_timestamp is required
                    success: true
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    data: Invalid phone number
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found (legacy envelope): no WhatsApp client exists for this user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 404
                data: Client not found
                success: true
        "500":
          description: "Internal Server Error (legacy envelope): the request could not be sent to the phone."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 500
                data: "Failed to send history sync request: websocket not connected"
                success: true

  /sync/full-history:
    post:
      tags:
        - Sync
      summary: Request a full history sync from the phone
      description: |
        Asks the primary phone to resend the chat history of all chats (full history sync on demand), like the initial sync after pairing. The history arrives asynchronously as `HistorySync` events; the HTTP response only confirms the request.

        **Rules:**
        - The body is optional and any decode error is ignored. Defaults: `days_limit` 30, `size_mb_limit` 1000, `include_groups` true, `include_calls` false. `days_limit` or `size_mb_limit` sent as `0` also fall back to the defaults.
        - `size_mb_limit` is used both as the full-sync size limit and as the storage quota announced to the phone.
        - `data.request_id` is a random 32-character hexadecimal ID and `data.config` echoes the effective limits.
        - Large histories can take minutes and generate many webhook events; the phone must stay online.
        - A disconnected client (**400**), a missing client (**404**) and a failed send (**500**) answer with the legacy envelope, where `data` is the message and `success` remains `true`. A never-paired session returns **500** `not logged in` in the standard envelope.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncFullHistoryRequest"
            examples:
              defaults:
                summary: Last 30 days including groups
                description: Same as sending no body.
                value:
                  days_limit: 30
                  size_mb_limit: 1000
                  include_groups: true
                  include_calls: false
              one_year_with_calls:
                summary: One year of history with call logs
                description: Larger window and size limit, also asking for the call log history.
                value:
                  days_limit: 365
                  size_mb_limit: 5000
                  include_groups: true
                  include_calls: true
      responses:
        "200":
          description: The full history request was sent to the primary phone.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncFullHistoryResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Full history sync requested successfully. History will arrive asynchronously via HistorySync events (webhook/websocket). This works exactly like the initial connection sync.
                  request_id: 9F3A6C1E2B7D4058A1C3E5F7092B4D6E
                  config:
                    days_limit: 30
                    size_mb_limit: 1000
                    include_groups: true
                    include_calls: false
                success: true
        "400":
          description: "Bad Request (legacy envelope): the WhatsApp client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 400
                data: Client not connected to WhatsApp
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found (legacy envelope): no WhatsApp client exists for this user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 404
                data: Client not found
                success: true
        "500":
          description: "Internal Server Error: the session was never paired (standard envelope), or the request could not be sent (legacy envelope)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                not_logged_in:
                  summary: Session not paired
                  value:
                    code: 500
                    error: not logged in
                    success: false
                send_failed:
                  summary: Request could not be sent (legacy envelope)
                  value:
                    code: 500
                    data: "Failed to send full history sync request: websocket not connected"
                    success: true

  /label/manage:
    post:
      tags:
        - Label
      summary: Create, edit or delete a label
      description: |
        Creates, edits or deletes a label (called a "list" in the personal WhatsApp app) by sending a `label_edit`
        app-state mutation. Labels are then applied to chats with `POST /label/chat`.

        **Operations:**
        - **Create:** omit `label_id` and send `label_name` (and optionally `label_color`). The gateway generates a free numeric ID and creates an active `CUSTOM` list; the response carries `label_id_generated: true` and `label_type: "CUSTOM"`.
        - **Edit:** send an existing `label_id` with the new `label_name` and `label_color`. If the label was observed from app state, its official fields (type, order, predefined ID) are preserved and `label_type` is returned.
        - **Delete:** send `label_id` with `delete: true`; `label_name` and `label_color` are ignored.

        **Rules:**
        - Official clients only display labels with numeric IDs. Generated IDs are the highest known numeric ID + 1 once labels were observed from app state, otherwise the current Unix time in seconds.
        - `label_name` is required unless `delete` is `true`; `label_id` is required when `delete` is `true`.
        - `label_color` is the palette index and is not range-checked by the gateway. When omitted it is sent as `0`.
        - The response `message` reads `Label updated successfully` for both create and edit (only delete reads `Label deleted successfully`); use `label_id_generated` to recognize a new label.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelManageRequest"
            examples:
              create_generated_id:
                summary: Create a label with a generated ID
                description: No `label_id`, so the gateway picks a free numeric ID and creates an active CUSTOM list.
                value:
                  label_name: Hot leads
                  label_color: 3
              update_existing:
                summary: Rename and recolor label 12
                description: Edits an existing label; official fields observed from app state are kept.
                value:
                  label_id: "12"
                  label_name: Follow-up
                  label_color: 5
              delete:
                summary: Delete label 12
                description: Only `label_id` and `delete` are needed to delete a label.
                value:
                  label_id: "12"
                  delete: true
      responses:
        "200":
          description: The `label_edit` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LabelManageResponse"
              examples:
                created_generated:
                  summary: Created without label_id
                  value:
                    code: 200
                    data:
                      success: true
                      message: Label updated successfully
                      label_id: "13"
                      label_id_generated: true
                      label_type: CUSTOM
                      label_name: Hot leads
                      label_color: 3
                    success: true
                updated_explicit_id:
                  summary: Edited a label not observed from app state
                  value:
                    code: 200
                    data:
                      success: true
                      message: Label updated successfully
                      label_id: "12"
                      label_id_generated: false
                      label_name: Follow-up
                      label_color: 5
                    success: true
                deleted:
                  summary: Deleted
                  value:
                    code: 200
                    data:
                      success: true
                      message: Label deleted successfully
                      label_id: "12"
                      label_id_generated: false
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `label_name` is missing on create/edit, or `label_id` is missing on delete."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                label_id_required_for_deletion:
                  summary: Delete without label_id
                  value:
                    code: 400
                    error: label_id required for deletion
                    success: false
                label_name_required:
                  summary: Create or edit without label_name
                  value:
                    code: 400
                    error: label_name required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `label_edit` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                failed_to_manage_label:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to manage label
                    success: false
  /label/chat:
    post:
      tags:
        - Label
      summary: Apply or remove a label on a chat
      description: |
        Applies a label to a chat (contact or group), or removes it, by sending a `label_jid` app-state mutation.
        This is the label association the official apps display in the chat list filters.

        **Rules:**
        - `phone` and `label_id` are required; `apply` defaults to `false`, so omitting it removes the label.
        - `phone` accepts a phone number, a user JID (`@s.whatsapp.net` / `@lid`) or a group JID (`@g.us`).
        - People are addressed by LID when the mapping is known; groups keep their JID. `index_jid` shows the JID written to the mutation.
        - The gateway does not check that `label_id` exists; use `GET /label/list` to find valid IDs.
        - A chat can carry several labels; each call changes only the given label.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelChatRequest"
            examples:
              apply_to_contact:
                summary: Apply label 12 to a contact
                description: The contact is addressed by LID in the mutation when the mapping is known.
                value:
                  phone: "5511999999999"
                  label_id: "12"
                  apply: true
              remove_from_group:
                summary: Remove label 12 from a group
                description: Group chats keep their `@g.us` JID in the mutation index.
                value:
                  phone: "120363000000000011@g.us"
                  label_id: "12"
                  apply: false
      responses:
        "200":
          description: The `label_jid` mutation was accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LabelChatResponse"
              examples:
                applied:
                  summary: Label applied to a contact
                  value:
                    code: 200
                    data:
                      success: true
                      message: Label applied successfully
                      jid: 5511999999999@s.whatsapp.net
                      label_id: "12"
                      applied: true
                      index_jid: 123456789012345@lid
                    success: true
                removed:
                  summary: Label removed from a group
                  value:
                    code: 200
                    data:
                      success: true
                      message: Label removed successfully
                      jid: 120363000000000011@g.us
                      label_id: "12"
                      applied: false
                      index_jid: 120363000000000011@g.us
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `phone` or `label_id` is missing, or `phone` is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: phone or label_id missing
                  value:
                    code: 400
                    error: missing phone or label_id in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `label_jid` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                failed_to_update_label_status:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to update label status
                    success: false
  /label/message:
    post:
      tags:
        - Label
      summary: Apply or remove a label on a message
      description: |
        Applies a label to a single message, or removes it, by sending a `label_message` app-state mutation.

        **Rules:**
        - Kept for compatibility only: the official WhatsApp apps do **not** display message labels (`label_message` is not an action of the official sync engine), so a 200 does not make a label visible on the phone. Use `POST /label/chat` to label conversations.
        - `phone`, `message_id` and `label_id` are required; `apply` defaults to `false`, so omitting it removes the label.
        - `phone` is the chat that holds the message (phone number, user JID or group JID). It is used as given; no LID translation is applied.
        - The gateway does not check that the message or the label exists.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelMessageRequest"
            examples:
              apply:
                summary: Apply label 12 to a message
                description: Labels message 3EB0C767D26A1D6B8A9F in a direct chat.
                value:
                  phone: "5511999999999"
                  message_id: 3EB0C767D26A1D6B8A9F
                  label_id: "12"
                  apply: true
              remove:
                summary: Remove label 12 from a group message
                description: Removes the label from a message inside a group chat.
                value:
                  phone: "120363000000000011@g.us"
                  message_id: 3EB0C767D26A1D6B8A9F
                  label_id: "12"
                  apply: false
      responses:
        "200":
          description: The `label_message` mutation was accepted by WhatsApp (not displayed by the official apps).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LabelMessageResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Label applied successfully
                  jid: 5511999999999@s.whatsapp.net
                  message_id: 3EB0C767D26A1D6B8A9F
                  label_id: "12"
                  applied: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, a required field is missing, or `phone` is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: phone, message_id or label_id missing
                  value:
                    code: 400
                    error: missing phone, message_id or label_id in payload
                    success: false
                invalid_phone_number:
                  summary: phone is not a valid number or JID
                  value:
                    code: 400
                    error: invalid phone number
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or WhatsApp rejected the `label_message` mutation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                failed_to_update_label_status:
                  summary: Mutation rejected
                  value:
                    code: 500
                    error: failed to update label status
                    success: false
  /label/list:
    get:
      tags:
        - Label
      summary: List labels and their chats
      description: |
        Lists the labels (called "lists" in the personal WhatsApp app) known to the gateway, with the chats that carry each one.

        **Behavior:**
        - **Without `refresh`:** returns only what this gateway process observed from app-state events or created/edited through the API. `complete` is `false`.
        - **With `refresh=true`:** runs a full sync of the `regular` app-state collection and rebuilds the list from the decrypted snapshot, including system lists (`UNREAD`, `FAVORITES`, `GROUPS`), `PREDEFINED` Business labels and `CUSTOM` lists. `complete: true` and `synced_at` are only returned after that sync succeeds.

        **Rules:**
        - Deleted labels are included with `deleted: true`.
        - Labels created by older gateway versions with a non-numeric ID (`label_<unix>`) are listed but not shown by the official apps.
        - The full sync does not emit label events to webhooks, and message labels (`POST /label/message`) are never part of this list.
        - The refresh errors (**500**, **501**, **502**, **503**) only happen with `refresh=true`. `refresh=true` is a heavy call; do not retry it automatically.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: refresh
          in: query
          required: false
          description: "`true` runs a full sync of the `regular` app-state collection before answering. Any value that is not a boolean is treated as `false`."
          schema:
            type: boolean
            default: false
          example: true
      responses:
        "200":
          description: Labels known to the gateway, sorted by order index.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LabelListResponse"
              examples:
                refreshed:
                  summary: After refresh=true
                  value:
                    code: 200
                    data:
                      labels:
                        - id: "11"
                          name: Unread
                          color: 0
                          deleted: false
                          predefined_id: 0
                          order_index: -12
                          is_active: true
                          type: UNREAD
                          is_immutable: false
                          source: app_state
                          updated_at: "2026-07-02T15:19:25-03:00"
                          chats: []
                        - id: "16"
                          name: New customer
                          color: 1
                          deleted: false
                          predefined_id: 1
                          order_index: 10
                          is_active: true
                          type: PREDEFINED
                          is_immutable: false
                          source: app_state
                          updated_at: "2026-07-02T15:19:25-03:00"
                          chats:
                            - 123456789012345@lid
                        - id: "1757862000"
                          name: VIP
                          color: 3
                          deleted: false
                          order_index: 1757862000
                          is_active: true
                          type: CUSTOM
                          is_immutable: false
                          source: api
                          updated_at: "2026-09-14T10:05:00Z"
                          chats:
                            - 120363000000000011@g.us
                      complete: true
                      observed_from_app_state: true
                      synced_at: "2026-09-15T01:39:35Z"
                      note: message labels (label_message) are not shown by the official apps
                    success: true
                observed_only:
                  summary: Without refresh, nothing observed yet
                  value:
                    code: 200
                    data:
                      labels: []
                      complete: false
                      observed_from_app_state: false
                      note: message labels (label_message) are not shown by the official apps
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error (only with `refresh=true`): no WhatsApp session, or the session is not logged in."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_logged_in:
                  summary: Session exists but is not paired
                  value:
                    code: 500
                    error: not logged in
                    success: false
        "501":
          description: "Not Implemented (only with `refresh=true`): the bundled WhatsApp library cannot return a full app-state snapshot."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 501
                error: full app state snapshot is not supported by the bundled WhatsApp library
                success: false
        "502":
          description: "Bad Gateway (only with `refresh=true`): the full sync of `regular` failed. No partial list is returned."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "full sync of regular failed: mismatching LTHash"
                success: false
        "503":
          description: "Service Unavailable (only with `refresh=true`): the WhatsApp client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: client not connected to WhatsApp
                success: false
  /group/create:
    post:
      tags:
        - Group
      summary: Create a group
      description: |
        Creates a WhatsApp group with the session as creator and super admin, adds the initial participants and returns
        the new group's full `GroupInfo`.

        **Rules:**
        - `Name` is required and must not be empty; `Participants` must contain at least one phone number or user JID.
        - One unparsable participant aborts the request with **400** before anything is created.
        - WhatsApp may refuse individual participants (privacy settings, not on WhatsApp). The group is still created: those entries come back in `Participants` with a non-zero `Error` and, when an invite is required, an `AddRequest` code.
        - The response is the raw `GroupInfo`; use `JID` for every later group call.

        **Use Cases:**
        - Open a dedicated group per customer, order or project
        - Create an internal team group from a CRM action
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateGroup"
            examples:
              phones:
                summary: Participants as phone numbers
                description: Numbers include the country code, without `+` or separators.
                value:
                  Name: Project Team
                  Participants:
                    - "5511999999999"
                    - "5511988888888"
              jids:
                summary: Participants as user JIDs
                description: Phone JIDs and LIDs are accepted; keys are matched case-insensitively.
                value:
                  name: Project Team
                  participants:
                    - 5511999999999@s.whatsapp.net
                    - 123456789012345@lid
      responses:
        "200":
          description: Group created; `data` is the new group's `GroupInfo`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupInfoResponse"
              example:
                code: 200
                data:
                  JID: "120363000000000011@g.us"
                  OwnerJID: 123456789012340@lid
                  OwnerPN: 5511977777777@s.whatsapp.net
                  Name: Project Team
                  NameSetAt: "2026-09-15T12:00:00Z"
                  NameSetBy: 123456789012340@lid
                  NameSetByPN: 5511977777777@s.whatsapp.net
                  Topic: ""
                  TopicID: ""
                  TopicSetAt: "0001-01-01T00:00:00Z"
                  TopicSetBy: ""
                  TopicSetByPN: ""
                  TopicDeleted: false
                  IsLocked: false
                  IsAnnounce: false
                  AnnounceVersionID: ""
                  IsEphemeral: false
                  DisappearingTimer: 0
                  IsIncognito: false
                  IsParent: false
                  DefaultMembershipApprovalMode: ""
                  LinkedParentJID: ""
                  IsDefaultSubGroup: false
                  IsJoinApprovalRequired: false
                  AddressingMode: lid
                  GroupCreated: "2026-09-15T12:00:00Z"
                  CreatorCountryCode: BR
                  ParticipantVersionID: ""
                  Participants:
                    - JID: 123456789012340@lid
                      PhoneNumber: 5511977777777@s.whatsapp.net
                      LID: 123456789012340@lid
                      Username: ""
                      IsAdmin: true
                      IsSuperAdmin: true
                      DisplayName: ""
                      Error: 0
                      AddRequest: null
                    - JID: 123456789012345@lid
                      PhoneNumber: 5511999999999@s.whatsapp.net
                      LID: 123456789012345@lid
                      Username: ""
                      IsAdmin: false
                      IsSuperAdmin: false
                      DisplayName: ""
                      Error: 0
                      AddRequest: null
                    - JID: 5511988888888@s.whatsapp.net
                      PhoneNumber: 5511988888888@s.whatsapp.net
                      LID: ""
                      Username: ""
                      IsAdmin: false
                      IsSuperAdmin: false
                      DisplayName: ""
                      Error: 403
                      AddRequest:
                        Code: AbCdEfGhIjKlMnOp
                        Expiration: "2026-09-18T12:00:00Z"
                  ParticipantCount: 3
                  MemberAddMode: admin_add
                  Suspended: false
                success: true
        "400":
          description: "Bad Request: invalid JSON, missing `Name` or `Participants`, or an unparsable participant."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_name:
                  summary: Name missing or empty
                  value:
                    code: 400
                    error: missing Name in Payload
                    success: false
                missing_participants:
                  summary: Participants missing or empty
                  value:
                    code: 400
                    error: missing Participants in Payload
                    success: false
                invalid_participant:
                  summary: A participant is not a valid number or JID
                  value:
                    code: 400
                    error: could not parse Participant Phone
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the creation (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Client disconnected (legacy envelope)
                  value:
                    code: 500
                    data: "failed to create group: websocket not connected"
                    success: true
  /group/locked:
    post:
      tags:
        - Group
      summary: Lock or unlock editing of group info
      description: |
        Controls the **Edit group settings** permission: when locked, only admins can change the group name, photo, description
        and disappearing-messages timer; when unlocked, every member can. The session must be an admin of the group.

        **Rules:**
        - `groupjid` is required and should be a group JID (`@g.us`).
        - `locked` defaults to `false` when omitted, which **unlocks** the group.
        - The response only confirms that WhatsApp accepted the change; use `GET /group/info` (`IsLocked`) to read the current state.

        **Use Cases:**
        - Keep a customer group's name and photo under admin control
        - Let project members update the group description themselves
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupLocked"
            examples:
              lock:
                summary: Only admins can edit group info
                description: Locks the name, photo and description to admins.
                value:
                  groupjid: "120363000000000011@g.us"
                  locked: true
              unlock:
                summary: Every member can edit group info
                description: Removes the admin-only restriction.
                value:
                  groupjid: "120363000000000011@g.us"
                  locked: false
      responses:
        "200":
          description: WhatsApp accepted the new locked setting.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group Locked setting updated successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `groupjid` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: groupjid is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the change (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Session is not an admin (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group locked: info query returned status 403: forbidden"
                    success: true
  /group/ephemeral:
    post:
      tags:
        - Group
      summary: Set the disappearing messages timer of a group
      description: |
        Turns disappearing messages on or off for a group. New messages sent after the change disappear once the chosen
        duration has passed; existing messages are not affected.

        **Rules:**
        - `groupjid` and `duration` are required.
        - `duration` accepts exactly `24h`, `7d`, `90d` or `off`. Any other value returns **400**.
        - When the group is locked (`POST /group/locked`), only admins can change the timer.

        **Use Cases:**
        - Keep short-lived coordination groups clean after 24 hours
        - Disable disappearing messages in a group used as an audit trail
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupEphemeral"
            examples:
              seven_days:
                summary: Messages disappear after 7 days
                description: Enables disappearing messages with the 7-day timer.
                value:
                  groupjid: "120363000000000011@g.us"
                  duration: 7d
              one_day:
                summary: Messages disappear after 24 hours
                description: Enables disappearing messages with the 24-hour timer.
                value:
                  groupjid: "120363000000000011@g.us"
                  duration: 24h
              "off":
                summary: Turn disappearing messages off
                description: New messages are kept until deleted.
                value:
                  groupjid: "120363000000000011@g.us"
                  duration: "off"
      responses:
        "200":
          description: WhatsApp accepted the new timer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Disappearing timer set successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, unparsable `groupjid`, missing `duration`, or a `duration` outside the accepted values."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: groupjid is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
                missing_duration:
                  summary: duration missing
                  value:
                    code: 400
                    error: missing Duration in Payload
                    success: false
                invalid_duration:
                  summary: duration not accepted
                  value:
                    code: 400
                    error: "invalid duration. Use: 24h, 7d, 90d, or off"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the change (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Client disconnected (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set disappearing timer: websocket not connected"
                    success: true
  /group/photo/remove:
    post:
      tags:
        - Group
      summary: Remove the group photo
      description: |
        Deletes the current profile photo of a group, leaving the default group avatar.

        **Rules:**
        - `groupjid` is required and should be a group JID (`@g.us`).
        - When the group is locked (`POST /group/locked`), only admins can remove the photo.
        - Removing a photo from a group that has none is decided by WhatsApp; an error comes back as **500** in the legacy envelope.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RemoveGroupPhoto"
            examples:
              remove:
                summary: Remove the photo of a group
                description: Only the group JID is needed.
                value:
                  groupjid: "120363000000000011@g.us"
      responses:
        "200":
          description: Photo removed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group Photo removed successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `groupjid` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: groupjid is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the removal (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Not allowed to change the photo (legacy envelope)
                  value:
                    code: 500
                    data: "failed to remove group photo: info query returned status 403: forbidden"
                    success: true
  /group/list:
    get:
      tags:
        - Group
      summary: List joined groups
      description: |
        Returns every group the session currently participates in, fetched live from WhatsApp, each as a full `GroupInfo`.

        **Rules:**
        - `ignoreParticipants=true` drops the `Participants` array from every group (`ParticipantCount` is kept). Only the exact string `true` enables it.
        - Use `IsParent` and `LinkedParentJID` to tell communities and community-linked groups apart from regular groups.
        - When the session is in no group, `Groups` is `null`.

        **Use Cases:**
        - Sync the group directory into a CRM
        - Find a group JID by name before calling other group routes
      security:
        - ApiKeyAuth: []
      parameters:
        - name: ignoreParticipants
          in: query
          required: false
          description: "`true` removes the participant list from every group to reduce the response size. Any other value keeps it."
          schema:
            type: boolean
            default: false
          example: true
      responses:
        "200":
          description: Joined groups.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupListResponse"
              examples:
                without_participants:
                  summary: With ignoreParticipants=true
                  value:
                    code: 200
                    data:
                      Groups:
                        - JID: "120363000000000011@g.us"
                          OwnerJID: 123456789012340@lid
                          OwnerPN: 5511977777777@s.whatsapp.net
                          Name: Project Team
                          NameSetAt: "2026-09-15T12:00:00Z"
                          NameSetBy: 123456789012340@lid
                          NameSetByPN: 5511977777777@s.whatsapp.net
                          Topic: "Weekly status and deadlines"
                          TopicID: "1757937600000000"
                          TopicSetAt: "2026-09-15T12:05:00Z"
                          TopicSetBy: 123456789012340@lid
                          TopicSetByPN: 5511977777777@s.whatsapp.net
                          TopicDeleted: false
                          IsLocked: true
                          IsAnnounce: false
                          AnnounceVersionID: "1757937600000000"
                          IsEphemeral: false
                          DisappearingTimer: 0
                          IsIncognito: false
                          IsParent: false
                          DefaultMembershipApprovalMode: ""
                          LinkedParentJID: ""
                          IsDefaultSubGroup: false
                          IsJoinApprovalRequired: false
                          AddressingMode: lid
                          GroupCreated: "2026-09-15T12:00:00Z"
                          CreatorCountryCode: BR
                          ParticipantVersionID: "1757937600000001"
                          Participants: null
                          ParticipantCount: 12
                          MemberAddMode: admin_add
                          Suspended: false
                    success: true
                no_groups:
                  summary: Session is in no group
                  value:
                    code: 200
                    data:
                      Groups: null
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or the WhatsApp query failed (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: Client disconnected (legacy envelope)
                  value:
                    code: 500
                    data: "failed to get group list: websocket not connected"
                    success: true
  /group/invitelink:
    get:
      tags:
        - Group
      summary: Get or reset the group invite link
      description: |
        Returns the `https://chat.whatsapp.com/...` invite link of a group. With `reset=true` WhatsApp revokes the current
        link and issues a new one. Only admins can read or reset the link.

        **Rules:**
        - `groupJID` is required. `reset` accepts any value Go parses as a boolean (`true`, `false`, `1`, `0`, `t`, `f`); anything else returns **400**.
        - Resetting invalidates every previously shared link immediately.
        - Not being an admin, not being a member, or an unknown group returns **500** in the legacy envelope with the WhatsApp reason.

        **Use Cases:**
        - Share the join link in an email or landing page
        - Rotate a leaked invite link
      security:
        - ApiKeyAuth: []
      parameters:
        - name: groupJID
          in: query
          required: true
          description: Group JID (`@g.us`).
          schema:
            type: string
          example: "120363000000000011@g.us"
        - name: reset
          in: query
          required: false
          description: "`true` revokes the current link and returns a new one."
          schema:
            type: boolean
            default: false
          example: false
      responses:
        "200":
          description: Current (or newly generated) invite link.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupInviteLinkResponse"
              example:
                code: 200
                data:
                  InviteLink: "https://chat.whatsapp.com/AbCdEfGhIjKlMnOpQrStUv"
                success: true
        "400":
          description: "Bad Request: missing `groupJID`, invalid `reset`, or unparsable `groupJID`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_group_jid:
                  summary: groupJID missing
                  value:
                    code: 400
                    error: missing groupJID parameter
                    success: false
                invalid_reset:
                  summary: reset is not a boolean
                  value:
                    code: 400
                    error: invalid reset parameter, must be true or false
                    success: false
                could_not_parse_group_jid:
                  summary: groupJID is invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp refused the request (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_admin:
                  summary: Session is not an admin (legacy envelope)
                  value:
                    code: 500
                    data: "Failed to get group invite link: you don't have the permission to get the group's invite link"
                    success: true
  /group/info:
    get:
      tags:
        - Group
      summary: Get group information
      description: |
        Returns the full `GroupInfo` of one group, fetched live from WhatsApp: name, description, settings, community links
        and the participant list with admin flags.

        **Rules:**
        - `groupJID` is required and must be a group the session belongs to.
        - People are identified by LID in groups using `AddressingMode: lid`; `PhoneNumber` carries the phone JID when WhatsApp shares it.
        - Not a member, or an unknown group, returns **500** in the legacy envelope with the WhatsApp reason.

        **Use Cases:**
        - Check whether the session is an admin before changing settings
        - Read the participant list to sync members into another system
      security:
        - ApiKeyAuth: []
      parameters:
        - name: groupJID
          in: query
          required: true
          description: Group JID (`@g.us`).
          schema:
            type: string
          example: "120363000000000011@g.us"
      responses:
        "200":
          description: Group information.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupInfoResponse"
              example:
                code: 200
                data:
                  JID: "120363000000000011@g.us"
                  OwnerJID: 123456789012340@lid
                  OwnerPN: 5511977777777@s.whatsapp.net
                  Name: Project Team
                  NameSetAt: "2026-09-15T12:00:00Z"
                  NameSetBy: 123456789012340@lid
                  NameSetByPN: 5511977777777@s.whatsapp.net
                  Topic: "Weekly status and deadlines"
                  TopicID: "1757937600000000"
                  TopicSetAt: "2026-09-15T12:05:00Z"
                  TopicSetBy: 123456789012340@lid
                  TopicSetByPN: 5511977777777@s.whatsapp.net
                  TopicDeleted: false
                  IsLocked: true
                  IsAnnounce: false
                  AnnounceVersionID: "1757937600000000"
                  IsEphemeral: true
                  DisappearingTimer: 604800
                  IsIncognito: false
                  IsParent: false
                  DefaultMembershipApprovalMode: ""
                  LinkedParentJID: "120363000000000033@g.us"
                  IsDefaultSubGroup: false
                  IsJoinApprovalRequired: true
                  AddressingMode: lid
                  GroupCreated: "2026-09-15T12:00:00Z"
                  CreatorCountryCode: BR
                  ParticipantVersionID: "1757937600000001"
                  Participants:
                    - JID: 123456789012340@lid
                      PhoneNumber: 5511977777777@s.whatsapp.net
                      LID: 123456789012340@lid
                      Username: ""
                      IsAdmin: true
                      IsSuperAdmin: true
                      DisplayName: ""
                      Error: 0
                      AddRequest: null
                    - JID: 123456789012345@lid
                      PhoneNumber: 5511999999999@s.whatsapp.net
                      LID: 123456789012345@lid
                      Username: ""
                      IsAdmin: false
                      IsSuperAdmin: false
                      DisplayName: ""
                      Error: 0
                      AddRequest: null
                  ParticipantCount: 2
                  MemberAddMode: admin_add
                  Suspended: false
                success: true
        "400":
          description: "Bad Request: `groupJID` is missing or cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_group_jid:
                  summary: groupJID missing
                  value:
                    code: 400
                    error: missing groupJID parameter
                    success: false
                could_not_parse_group_jid:
                  summary: groupJID is invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or the WhatsApp query failed (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_a_member:
                  summary: Session is not in the group (legacy envelope)
                  value:
                    code: 500
                    data: "Failed to get group info: you're not participating in that group"
                    success: true
  /group/photo:
    post:
      tags:
        - Group
      summary: Set the group photo
      description: |
        Uploads a new profile photo for a group and returns the WhatsApp picture ID. The image is sent as a base64 data URL
        and must contain JPEG bytes, because WhatsApp only accepts JPEG group photos.

        **Rules:**
        - `GroupJID` and `Image` are required.
        - `Image` must start with `data:image` and decode as a data URL; otherwise **400**.
        - The decoded bytes must be JPEG (start with `FF D8 FF`). PNG, GIF or WebP data URLs pass the prefix check but are rejected with **400**, even though the prefix error message lists them.
        - WhatsApp may reject images that are not square or are too small; that failure comes back as **500** in the legacy envelope.
        - When the group is locked (`POST /group/locked`), only admins can change the photo.

        **Use Cases:**
        - Brand customer groups with the company logo
        - Refresh an event group's cover image
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupPhoto"
            examples:
              jpeg_data_url:
                summary: JPEG image as a data URL
                description: The base64 payload is shortened here; send the full JPEG file content.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Image: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AKp//2Q=="
      responses:
        "200":
          description: Photo set; `PictureID` is the ID assigned by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupPhotoSetResponse"
              example:
                code: 200
                data:
                  Details: Group Photo set successfully
                  PictureID: "1757862000"
                success: true
        "400":
          description: "Bad Request: invalid JSON, unparsable `GroupJID`, missing `Image`, not a data URL, empty image, or not a JPEG."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: GroupJID is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
                missing_image:
                  summary: Image missing
                  value:
                    code: 400
                    error: missing Image in Payload
                    success: false
                not_a_data_url:
                  summary: Image does not start with data:image
                  value:
                    code: 400
                    error: "image data should start with \"data:image/\" (supported formats: jpeg, png, gif, webp)"
                    success: false
                invalid_base64:
                  summary: Data URL cannot be decoded
                  value:
                    code: 400
                    error: could not decode base64 encoded data from payload
                    success: false
                empty_image:
                  summary: Data URL has no bytes
                  value:
                    code: 400
                    error: no image data found in payload
                    success: false
                not_jpeg:
                  summary: Decoded image is not a JPEG
                  value:
                    code: 400
                    error: image must be in JPEG format. WhatsApp only accepts JPEG images for group photos
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the image (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Image rejected (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group photo: the given data is not a valid image"
                    success: true
  /group/leave:
    post:
      tags:
        - Group
      summary: Leave a group
      description: |
        Removes the session from a group. The group keeps existing for the other participants and the chat history stays
        on the linked devices.

        **Rules:**
        - `GroupJID` is required and should be a group JID (`@g.us`).
        - The gateway does not promote anyone first: if the session is the only admin, promote another member with `POST /group/updateparticipants` before leaving.
        - After leaving, group routes that need membership (info, invite link, settings) fail for this group.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupLeave"
            examples:
              leave:
                summary: Leave a group
                description: Only the group JID is needed.
                value:
                  GroupJID: "120363000000000011@g.us"
      responses:
        "200":
          description: The session left the group.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group left successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `GroupJID` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: GroupJID is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the request (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Not a member of the group (legacy envelope)
                  value:
                    code: 500
                    data: "failed to leave group: info query returned status 404: item-not-found"
                    success: true
  /group/name:
    post:
      tags:
        - Group
      summary: Rename a group
      description: |
        Changes the subject (name) of a group. Every participant sees a system message announcing the new name.

        **Rules:**
        - `GroupJID` and `Name` are required; an empty `Name` returns **400**.
        - The gateway does not check the length; WhatsApp enforces its own subject limit and rejects longer names with **500** in the legacy envelope.
        - When the group is locked (`POST /group/locked`), only admins can rename it.

        **Use Cases:**
        - Include a ticket number or status in a support group name
        - Rename an event group after the date changes
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupName"
            examples:
              rename:
                summary: Rename a group
                description: Sets a new subject shown to every participant.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Name: Customer Success Team
              with_status:
                summary: Name carrying a ticket status
                description: Emoji and digits are allowed in the subject.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Name: "Ticket 4821 - Resolved ✅"
      responses:
        "200":
          description: Group renamed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group Name set successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, unparsable `GroupJID`, or missing `Name`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: GroupJID is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
                missing_name:
                  summary: Name missing or empty
                  value:
                    code: 400
                    error: missing Name in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the name (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Not allowed to rename (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group name: info query returned status 403: forbidden"
                    success: true
  /group/topic:
    post:
      tags:
        - Group
      summary: Set the group description
      description: |
        Changes the description (topic) shown in the group info screen.

        **Rules:**
        - `GroupJID` and `Topic` are required. An empty `Topic` returns **400**, so this route cannot clear a description.
        - The gateway does not check the length; WhatsApp enforces its own limit and rejects longer text with **500** in the legacy envelope.
        - When the group is locked (`POST /group/locked`), only admins can change the description.

        **Use Cases:**
        - Publish group rules or opening hours
        - Keep a link to the current agenda in a team group
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupTopic"
            examples:
              rules:
                summary: Publish group rules
                description: Multi-line text is allowed; use `\n` for line breaks.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Topic: "Support hours: Mon-Fri, 9am-6pm.

                    Please keep messages on topic."
      responses:
        "200":
          description: Description updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group Topic set successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, unparsable `GroupJID`, or missing `Topic`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: GroupJID is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
                missing_topic:
                  summary: Topic missing or empty
                  value:
                    code: 400
                    error: missing Topic in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the description (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Not allowed to change the description (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group topic: info query returned status 403: forbidden"
                    success: true
  /group/announce:
    post:
      tags:
        - Group
      summary: Allow only admins to send messages
      description: |
        Controls the **Send messages** permission of a group. With announce mode on, only admins can send messages;
        every member can still read them. The session must be an admin of the group.

        **Rules:**
        - `GroupJID` is required and should be a group JID (`@g.us`).
        - `Announce` defaults to `false` when omitted, which lets every member send messages again.
        - Read the current state from `GET /group/info` (`IsAnnounce`).

        **Use Cases:**
        - Broadcast-style groups for announcements and newsletters
        - Temporarily mute a group during a live event
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupAnnounce"
            examples:
              admins_only:
                summary: Only admins can send messages
                description: Turns announce mode on.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Announce: true
              everyone:
                summary: Every member can send messages
                description: Turns announce mode off.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Announce: false
      responses:
        "200":
          description: WhatsApp accepted the new announce setting.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group Announce set successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `GroupJID` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: GroupJID is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the change (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: Session is not an admin (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group announce: info query returned status 403: forbidden"
                    success: true
  /group/memberaddmode:
    post:
      tags:
        - Group
      summary: Set who can add members to a group
      description: |
        Changes the **Add other members** permission of a group: either every member or only admins can add new participants.
        The gateway sends the `member_add_mode` group IQ; the session must be an admin of the group for WhatsApp to accept it.

        **Rules:**
        - `groupjid` and `mode` are required. `mode` is validated first: any value other than `admin_add` or `all_member_add` returns **400** before the JID is parsed.
        - `groupjid` should be a group JID (`@g.us`). A value without `@` is parsed as a phone number and is rejected by WhatsApp, not by the gateway.

        **Use Cases:**
        - Lock membership growth of a support group to admins
        - Let every member invite colleagues in an internal team group
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupMemberAddMode"
            examples:
              admins_only:
                summary: Only admins can add members
                description: Sets the "Add other members" permission to admins only.
                value:
                  groupjid: "120363000000000011@g.us"
                  mode: admin_add
              all_members:
                summary: All members can add members
                description: Allows every participant to add new members.
                value:
                  groupjid: "120363000000000011@g.us"
                  mode: all_member_add
      responses:
        "200":
          description: WhatsApp accepted the new member add mode.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group member add mode set successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `mode` is not an accepted value, or `groupjid` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                invalid_mode:
                  summary: mode is missing or not accepted
                  value:
                    code: 400
                    error: invalid mode, must be 'admin_add' or 'all_member_add'
                    success: false
                could_not_parse_group_jid:
                  summary: groupjid is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the change (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: WhatsApp rejected the change (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group member add mode: info query returned status 403: forbidden"
                    success: true
  /group/joinapproval:
    post:
      tags:
        - Group
      summary: Turn admin approval for new members on or off
      description: |
        Changes the **Approve new members** permission of a group. When it is on, people joining through an invite link
        become pending requests that an admin must approve (see `POST /community/requests` and `POST /community/requests/update`).
        The gateway sends the `membership_approval_mode` group IQ; the session must be an admin of the group.

        **Rules:**
        - `groupjid` is required. `require_approval` defaults to `false` when omitted, so an empty body field turns approval **off**.

        **Use Cases:**
        - Screen who enters a public group shared by link
        - Reopen a group for direct joins after an event ends
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupJoinApproval"
            examples:
              require:
                summary: Require admin approval
                description: New members who use an invite link wait for an admin to approve them.
                value:
                  groupjid: "120363000000000011@g.us"
                  require_approval: true
              open:
                summary: Join without approval
                description: Anyone with a valid invite link joins immediately.
                value:
                  groupjid: "120363000000000011@g.us"
                  require_approval: false
      responses:
        "200":
          description: WhatsApp accepted the new join approval mode.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group join approval mode set successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `groupjid` cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: groupjid is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the change (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                whatsapp_rejected:
                  summary: WhatsApp rejected the change (legacy envelope)
                  value:
                    code: 500
                    data: "failed to set group join approval mode: info query returned status 403: forbidden"
                    success: true
  /group/join:
    post:
      tags:
        - Group
      summary: Join a group with an invite link
      description: |
        Joins a group using an invite code or a full `https://chat.whatsapp.com/...` link. Preview the group first with
        `POST /group/inviteinfo` if needed.

        **Rules:**
        - `Code` is required. The `https://chat.whatsapp.com/` prefix and any query string are stripped by the library.
        - The response only confirms the join; it does not include the group JID. Use `POST /group/inviteinfo` or `GET /group/list` to obtain it.
        - When the group requires admin approval, WhatsApp registers a join request instead of adding the session immediately.
        - Revoked or malformed links return **500** in the legacy envelope with the WhatsApp reason.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupJoin"
            examples:
              code:
                summary: Invite code only
                description: The part after `chat.whatsapp.com/`.
                value:
                  Code: AbCdEfGhIjKlMnOpQrStUv
              full_link:
                summary: Full invite link
                description: The link prefix is removed before the request is sent.
                value:
                  Code: "https://chat.whatsapp.com/AbCdEfGhIjKlMnOpQrStUv"
      responses:
        "200":
          description: WhatsApp accepted the join.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group joined successfully
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `Code` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_code:
                  summary: Code missing or empty
                  value:
                    code: 400
                    error: missing Code in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp refused the join (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                link_revoked:
                  summary: Link revoked (legacy envelope)
                  value:
                    code: 500
                    data: "failed to join group: that group invite link has been revoked"
                    success: true
  /group/inviteinfo:
    post:
      tags:
        - Group
      summary: Preview a group from its invite link
      description: |
        Returns the `GroupInfo` of a group from an invite code or link **without joining it**, so the name, description,
        size and settings can be checked first.

        **Rules:**
        - `Code` is required. The `https://chat.whatsapp.com/` prefix and any query string are stripped by the library.
        - Fields WhatsApp does not share with non-members come back empty, zero or `null` (the participant list is often `null`).
        - Revoked or malformed links return **500** in the legacy envelope with the WhatsApp reason.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupInviteInfo"
            examples:
              code:
                summary: Invite code only
                description: The part after `chat.whatsapp.com/`.
                value:
                  Code: AbCdEfGhIjKlMnOpQrStUv
              full_link:
                summary: Full invite link
                description: The link prefix is removed before the query is sent.
                value:
                  Code: "https://chat.whatsapp.com/AbCdEfGhIjKlMnOpQrStUv"
      responses:
        "200":
          description: Group preview.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupInfoResponse"
              example:
                code: 200
                data:
                  JID: "120363000000000011@g.us"
                  OwnerJID: 123456789012340@lid
                  OwnerPN: ""
                  Name: Project Team
                  NameSetAt: "2026-09-15T12:00:00Z"
                  NameSetBy: ""
                  NameSetByPN: ""
                  Topic: "Weekly status and deadlines"
                  TopicID: ""
                  TopicSetAt: "2026-09-15T12:05:00Z"
                  TopicSetBy: ""
                  TopicSetByPN: ""
                  TopicDeleted: false
                  IsLocked: true
                  IsAnnounce: false
                  AnnounceVersionID: ""
                  IsEphemeral: false
                  DisappearingTimer: 0
                  IsIncognito: false
                  IsParent: false
                  DefaultMembershipApprovalMode: ""
                  LinkedParentJID: ""
                  IsDefaultSubGroup: false
                  IsJoinApprovalRequired: true
                  AddressingMode: lid
                  GroupCreated: "2026-09-15T12:00:00Z"
                  CreatorCountryCode: ""
                  ParticipantVersionID: ""
                  Participants: null
                  ParticipantCount: 12
                  MemberAddMode: ""
                  Suspended: false
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `Code` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_code:
                  summary: Code missing or empty
                  value:
                    code: 400
                    error: missing Code in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp refused the query (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                link_invalid:
                  summary: Malformed link (legacy envelope)
                  value:
                    code: 500
                    data: "failed to get group invite info: that group invite link is not valid"
                    success: true
  /group/updateparticipants:
    post:
      tags:
        - Group
      summary: Add, remove, promote or demote group participants
      description: |
        Applies one participant action to a list of people in a group: add them, remove them, promote them to admin or
        demote them. The session must be an admin of the group.

        **Rules:**
        - `GroupJID`, `Phone` (at least one entry) and `Action` are required. `Action` is case-sensitive: `add`, `remove`, `promote` or `demote`.
        - Validation order: `GroupJID`, then `Phone`, then `Action`. One unparsable phone aborts the request with **400**.
        - **200 does not mean every participant changed.** WhatsApp answers per participant, but this route discards that list; confirm with `GET /group/info`.
        - Whole-request failures (not an admin, not a member) return **500** in the legacy envelope with the WhatsApp reason.

        **Use Cases:**
        - Add a new customer contact to an existing support group
        - Promote a colleague to admin before leaving a group
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGroupParticipants"
            examples:
              add:
                summary: Add two participants
                description: Phone numbers with country code; user JIDs also work.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Phone:
                    - "5511999999999"
                    - "5511988888888"
                  Action: add
              remove:
                summary: Remove a participant
                description: Removes one member from the group.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Phone:
                    - "5511988888888"
                  Action: remove
              promote:
                summary: Promote to admin
                description: Keys are matched case-insensitively; LIDs are accepted.
                value:
                  groupjid: "120363000000000011@g.us"
                  phone:
                    - 123456789012345@lid
                  action: promote
              demote:
                summary: Demote an admin
                description: The participant stays in the group as a regular member.
                value:
                  GroupJID: "120363000000000011@g.us"
                  Phone:
                    - "5511999999999"
                  Action: demote
      responses:
        "200":
          description: WhatsApp accepted the request. Per-participant results are not returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group Participants updated successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, unparsable `GroupJID`, missing or invalid `Phone`, or missing or unknown `Action`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                could_not_parse_group_jid:
                  summary: GroupJID is missing or invalid
                  value:
                    code: 400
                    error: could not parse Group JID
                    success: false
                missing_phone:
                  summary: Phone missing or empty
                  value:
                    code: 400
                    error: missing Phone in Payload
                    success: false
                invalid_phone:
                  summary: A phone is not a valid number or JID
                  value:
                    code: 400
                    error: could not parse Phone
                    success: false
                missing_action:
                  summary: Action missing
                  value:
                    code: 400
                    error: missing Action in Payload
                    success: false
                invalid_action:
                  summary: Action not accepted
                  value:
                    code: 400
                    error: invalid Action in Payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session (plain error), or WhatsApp rejected the request (legacy envelope with the text in `data` and `success: true`)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/LegacyErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_admin:
                  summary: Session is not an admin (legacy envelope)
                  value:
                    code: 500
                    data: "failed to change participant group: info query returned status 403: forbidden"
                    success: true
  /admin/global/stats:
    get:
      tags:
        - Admin Global
      summary: Get global dispatcher statistics
      description: |
        Returns a live snapshot of the global event dispatcher: which transports are active, per-transport configuration and health, persistent buffer counters, call event ingress and dispatch rules.

        **Features:**
        - One object per transport (`webhook`, `rabbitmq`, `sqs`, `redis`, `websocket`); a transport that is not configured is reported as `{"enabled": false}`.
        - Each configured transport includes a `health` object (connection state, circuit breaker and delivery metrics where available).
        - `buffer` exposes the persistent buffer counters (pending, enqueued, dequeued, acknowledged, requeued, replayed, archived, pruned).
        - `global_rabbitmq_queues` lists the queues declared by the global RabbitMQ publisher when it exists.

        **Rules:**
        - Values are read from memory at request time; nothing is reset by this call.
        - Connection URLs are returned as configured (`webhook.url`, `rabbitmq.url`), so credentials embedded in them are visible to the admin.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Global dispatcher snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminGlobalStatsResponse"
              example:
                code: 200
                data:
                  enabled: true
                  transportCount: 2
                  transports:
                    - global-webhook
                    - global-rabbitmq
                  callEventIngress:
                    enabled: false
                  callRecordingFinalizer:
                    enabled: false
                  webhook:
                    enabled: true
                    url: https://hooks.example.com/global
                    events:
                      - All
                    timeout: 30s
                    maxRetries: 3
                    retryDelay: 2s
                    maxRetryDelay: 30s
                    backoffFactor: 2
                    maxConcurrency: 50
                    activeRequests: 1
                    availableSlots: 49
                    metrics:
                      sent: 1520
                      failed: 3
                      latencyMs: 84.5
                      lastError: ""
                    health:
                      enabled: true
                      url: https://hooks.example.com/global
                      metrics:
                        sent: 1520
                        failed: 3
                        latencyMs: 84.5
                        lastError: ""
                  rabbitmq:
                    enabled: true
                    connected: true
                    url: amqp://zuckzapgo:example-password@rabbitmq.internal:5672/
                    exchange: zuckzapgo.global
                    exchangeType: topic
                    queue: zuckzapgo.events
                    queueType: classic
                    routingKey: events.#
                    events:
                      - All
                    connectionPoolSize: 50
                    healthyConnections: 50
                    queueDepth: 0
                    totalEvents: 98231
                    successCount: 98230
                    failureCount: 1
                    circuitBreakerState: CLOSED
                    latencyP99Ms: 12
                    health:
                      enabled: true
                      connected: true
                      circuitBreakerState: CLOSED
                  sqs:
                    enabled: false
                  redis:
                    enabled: false
                  websocket:
                    enabled: false
                  s3:
                    enabled: true
                    bucket: zuckzapgo-media
                    region: us-east-1
                    endpoint: ""
                    mediaDelivery: both
                    hasClient: true
                  buffer:
                    enabled: true
                    pending: 4
                    enqueued: 99755
                    dequeued: 99751
                    ackSuccess: 99748
                    ackFailures: 3
                    requeued: 3
                    replayed: 0
                    archiveStored: 99748
                    archivePruned: 52000
                  dispatchRules:
                    global-webhook:
                      enabled: true
                      allowAll: true
                      events:
                        - All
                  global_rabbitmq_queues:
                    dynamicQueues: false
                    createdQueues: 1
                    queueNames:
                      - zuckzapgo.events
                    staticQueue: zuckzapgo.events
                    exchangeName: zuckzapgo.global
                    exchangeType: topic
                    routingKeyPattern: events.#
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/global/test:
    post:
      tags:
        - Admin Global
      summary: Test global transport connectivity
      description: |
        Runs a live connectivity check against every global transport and reports the outcome per transport, so an operator can confirm credentials and reachability after changing the environment.

        **Features:**
        - Webhook, RabbitMQ, SQS, Redis and WebSocket are each tested with their own connection check; `success` is `true` only when the check returned no error.
        - S3 is reported as successful when it is enabled and its client exists; no object is written.
        - Adds `global_rabbitmq_health` and `global_rabbitmq_queue_stats` when the global RabbitMQ publisher exists.

        **Rules:**
        - A failing transport does not fail the request: the response is still **200** with that transport's `error` filled.
        - Transports that are not configured report `enabled: false`, `success: false` and a "not configured" error.
        - No request body is read.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Per-transport test results.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminGlobalTestResponse"
              examples:
                webhook_ok_rabbitmq_down:
                  summary: Webhook reachable, RabbitMQ failing
                  value:
                    code: 200
                    data:
                      webhook:
                        enabled: true
                        success: true
                        error: null
                      rabbitmq:
                        enabled: true
                        success: false
                        error: "dial tcp 10.0.0.12:5672: connect: connection refused"
                      sqs:
                        enabled: false
                        success: false
                        error: Global SQS not configured
                      redis:
                        enabled: false
                        success: false
                        error: Global Redis not configured
                      websocket:
                        enabled: false
                        success: false
                        error: Global WebSocket not configured
                      s3:
                        enabled: true
                        success: true
                        bucket: zuckzapgo-media
                        region: us-east-1
                        error: null
                      global_rabbitmq_health:
                        enabled: true
                        connected: false
                        circuitBreakerState: OPEN
                      global_rabbitmq_queue_stats:
                        dynamicQueues: false
                        createdQueues: 0
                        queueNames: []
                        staticQueue: zuckzapgo.events
                        exchangeName: zuckzapgo.global
                        exchangeType: topic
                        routingKeyPattern: events.#
                    success: true
                nothing_configured:
                  summary: No global transport configured
                  value:
                    code: 200
                    data:
                      webhook:
                        enabled: false
                        success: false
                        error: Global webhook not configured
                      rabbitmq:
                        enabled: false
                        success: false
                        error: Global RabbitMQ not configured
                      sqs:
                        enabled: false
                        success: false
                        error: Global SQS not configured
                      redis:
                        enabled: false
                        success: false
                        error: Global Redis not configured
                      websocket:
                        enabled: false
                        success: false
                        error: Global WebSocket not configured
                      s3:
                        enabled: false
                        success: false
                        error: Global S3 not configured
                    success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/global/config:
    get:
      tags:
        - Admin Global
      summary: Get global transport configuration
      description: |
        Reads the global webhook, RabbitMQ, skip and S3 settings from the environment at request time and returns them together with a reference of the supported `GLOBAL_*` environment variables.

        **Features:**
        - Values are loaded fresh from the environment on every call, so they show what a reload would apply, even before `POST /admin/global/config/reload`.
        - `environment_variables` maps each supported variable name to a short explanation.

        **Rules:**
        - Only `s3.secretKey` is masked (`****` plus its last 4 characters). `s3.accessKey` and `rabbitmq.url` are returned as configured, including any credentials.
        - Durations are Go duration strings (for example `30s`).
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Global configuration read from the environment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminGlobalConfigResponse"
              example:
                code: 200
                data:
                  webhook:
                    enabled: true
                    url: https://hooks.example.com/global
                    events:
                      - All
                    timeout: 30s
                    retryCount: 3
                    retryDelay: 2s
                    maxRetryDelay: 30s
                    backoffFactor: 2
                    maxConcurrency: 50
                  rabbitmq:
                    enabled: true
                    url: amqp://zuckzapgo:example-password@rabbitmq.internal:5672/
                    events:
                      - All
                    exchange: zuckzapgo.global
                    exchangeType: topic
                    queue: zuckzapgo.events
                    queueType: classic
                    routingKey: events.#
                    durable: true
                    autoDelete: false
                    noWait: false
                    exclusive: false
                    deliveryMode: 2
                  skip:
                    skipGroups: false
                    skipNewsletters: true
                    skipBroadcasts: true
                    skipOwnMessages: false
                    skipCalls: false
                    callRejectMessage: Sorry, I cannot take calls at the moment.
                    callRejectType: busy
                    skipMediaDownload: false
                  s3:
                    enabled: true
                    endpoint: ""
                    region: us-east-1
                    bucket: zuckzapgo-media
                    accessKey: AKIAEXAMPLEACCESSKEY
                    secretKey: "****6789"
                    pathStyle: false
                    publicURL: ""
                    mediaDelivery: both
                    retentionDays: 30
                    disableACL: true
                  environment_variables:
                    GLOBAL_WEBHOOK_ENABLED: Set to 'true' to enable global webhook
                    GLOBAL_WEBHOOK_URL: URL for global webhook endpoint
                    GLOBAL_RABBITMQ_ENABLED: Set to 'true' to enable global RabbitMQ
                    GLOBAL_S3_RETENTION_DAYS: Retention days for media (0 = no expiration)
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/global/config/reload:
    post:
      tags:
        - Admin Global
      summary: Reload global transport configuration
      description: |
        Re-reads the global transport settings from the environment and applies them to the running dispatcher without a restart, then returns the resulting dispatcher statistics.

        **Rules:**
        - Always answers **200**; problems while re-initializing a transport are logged and show up in the returned `stats` (for example `connected: false`).
        - The environment of the running process is read, so variables must already be set in that process (a changed `.env` file on disk is not re-read by this call).
        - No request body is read.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Configuration reloaded; `data.stats` has the same shape as `GET /admin/global/stats` (without `global_rabbitmq_queues`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminGlobalReloadResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Global configurations reloaded successfully
                  stats:
                    enabled: true
                    transportCount: 1
                    transports:
                      - global-webhook
                    webhook:
                      enabled: true
                      url: https://hooks.example.com/global
                    rabbitmq:
                      enabled: false
                    sqs:
                      enabled: false
                    redis:
                      enabled: false
                    websocket:
                      enabled: false
                    buffer:
                      enabled: true
                      pending: 0
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
  /admin/global/s3-retention/stats:
    get:
      tags:
        - Admin Global
      summary: Get S3 retention cleanup statistics
      operationId: getS3RetentionStats
      description: |
        Returns the counters and configuration of the global S3 retention worker, which periodically deletes objects older than each user's `s3_retention_days`.

        **Features:**
        - Cumulative counters since startup: objects deleted, bytes reclaimed, users processed, errors and completed cycles.
        - `currently_running` tells whether a cleanup cycle is in progress right now; `running` whether the worker loop is started.
        - `config` shows the cycle interval, concurrency, batch size and per-cycle object limit.

        **Rules:**
        - **503** when the retention manager was never initialized (for example, no S3 configuration at startup).
      security:
        - AdminAuth: []
      responses:
        "200":
          description: S3 retention worker metrics.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminS3RetentionStatsResponse"
              example:
                code: 200
                data:
                  enabled: true
                  running: true
                  currently_running: false
                  last_run_time: "2026-09-15T10:30:00Z"
                  last_run_duration: 2m30.5s
                  objects_deleted: 15420
                  bytes_reclaimed: 1073741824
                  error_count: 2
                  users_processed: 45
                  cycle_count: 24
                  config:
                    interval: 1h0m0s
                    max_concurrency: 5
                    batch_size: 1000
                    objects_per_cycle: 10000
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the retention manager is not initialized, or the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_initialized:
                  summary: Retention manager not initialized
                  value:
                    code: 503
                    error: S3 retention manager not initialized
                    success: false
                admin_not_configured:
                  summary: Gateway started without an admin token
                  value:
                    code: 503
                    error: admin not configured
                    success: false
  /admin/global/s3-retention/trigger:
    post:
      tags:
        - Admin Global
      summary: Trigger an S3 retention cleanup cycle
      operationId: triggerS3RetentionCycle
      description: |
        Asks the global S3 retention worker to start a cleanup cycle now instead of waiting for the next interval. The cycle runs in the background; follow its progress with `GET /admin/global/s3-retention/stats`.

        **Rules:**
        - Returns immediately with **200**; the response does not wait for the cycle or report its results.
        - **503** when the retention manager is not initialized or its worker loop is not running.
        - No request body is read.

        **Use Cases:**
        - Reclaim space right after lowering `s3_retention_days` for a user
        - Verify retention settings in a test environment
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Cycle requested. The envelope carries `message` and no `data` key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminS3RetentionTriggerResponse"
              example:
                code: 200
                message: S3 retention cycle triggered. Check /admin/global/s3-retention/stats for progress.
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the retention manager is not initialized or not running, or the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_initialized:
                  summary: Retention manager not initialized
                  value:
                    code: 503
                    error: S3 retention manager not initialized
                    success: false
                not_running:
                  summary: Worker loop stopped
                  value:
                    code: 503
                    error: S3 retention manager is not running
                    success: false
                admin_not_configured:
                  summary: Gateway started without an admin token
                  value:
                    code: 503
                    error: admin not configured
                    success: false
  /admin/global/event/test:
    post:
      tags:
        - Admin Global
      summary: Dispatch a test event through global transports
      description: |
        Builds a synthetic event and hands it to the global dispatcher, which delivers it to every enabled global transport (subject to their event filters), to verify end-to-end delivery.

        **Rules:**
        - All body fields are optional: `event_type` defaults to `TestEvent`, `user_id` to `admin-test`, `user_token` to the value of `user_id`, and `data` to a small test object with `test`, `timestamp` and `message`.
        - When `user_id` matches a stored user, its name and JID are attached to the event; otherwise they are empty.
        - The response only confirms the hand-off to the dispatcher, not delivery.

        **Known Issue:**
        - The current handler reads a user context that the admin route chain never sets, so a request with a valid JSON body aborts before dispatching: the connection is closed without an HTTP response and no event is sent. Only the invalid-JSON path (**400**) answers today. The 200 response below documents the intended contract.
      security:
        - AdminAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GlobalTestEvent"
            examples:
              defaults:
                summary: All defaults
                description: Sends a `TestEvent` for the synthetic user `admin-test` with the default test data.
                value: {}
              stored_user:
                summary: Custom event for a stored user
                description: Uses a real user ID so the event carries that user's name and JID, with custom data.
                value:
                  event_type: Message
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  user_token: a1b2c3d4e5f6a7b8c9d0
                  data:
                    test: true
                    message: Delivery check from the admin panel
      responses:
        "200":
          description: Event handed to the global dispatcher (intended contract; see the known issue).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminGlobalEventTestResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Test event dispatched to global systems
                  eventType: TestEvent
                  userID: admin-test
                  userToken: admin-test
                  data:
                    test: true
                    timestamp: 1757937600
                    message: This is a test event sent from the admin panel
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: invalid JSON
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  # Monitoring Endpoints
  /admin/events/stream:
    get:
      tags:
        - Admin Monitoring
      summary: Stream monitoring events (Server-Sent Events)
      description: |
        Opens a Server-Sent Events stream that pushes every event the global dispatcher records, in real time, for live dashboards. Browsers can connect with `EventSource` by passing a session token in the query string.

        **Authentication:**
        - `session` query parameter with a token from `POST /admin/events/session` (checked first), or
        - `Authorization: Bearer <admin token>` header. User instance tokens are not accepted.
        - This route is outside the admin middleware: errors are **plain text**, not the JSON envelope.

        **Features:**
        - Each event is sent as an SSE frame: `id: <event id>`, `event: <event type>`, `data: <MonitoringEvent JSON>`. Heartbeats are sent as the comment line `:heartbeat`.
        - With `since_id`, up to 50 stored events newer than that ID are sent first (catch-up after a reconnect).
        - `user_id`, `event_type`, `status`, `only_failed` and `since_time` narrow which events are forwarded. Live events are published as soon as they are recorded, with status `pending`, so `status=failed` and `only_failed=true` mainly affect the catch-up batch.

        **Rules:**
        - Query parameters are parsed before authentication, so a malformed filter returns **400** even without credentials.
        - **503** when monitoring is disabled (`MONITORING_ENABLED=false`) or the client limit (`MONITORING_MAX_CLIENTS`) is reached.
        - The connection stays open until the client disconnects; do not call it from Swagger UI.
      security:
        - AdminAuth: []
      parameters:
        - name: session
          in: query
          required: false
          description: Monitoring session token from `POST /admin/events/session`. When present, the `Authorization` header is ignored.
          schema:
            type: string
          example: session_1757937600123456789_9f8e7d6c5b4a3210
        - name: user_id
          in: query
          required: false
          description: Only forward events of this user ID.
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
        - name: event_type
          in: query
          required: false
          description: Only forward events of this event type (exact, case-sensitive).
          schema:
            type: string
          example: Message
        - name: status
          in: query
          required: false
          description: Only forward events with this processing status.
          schema:
            type: string
            enum:
              - pending
              - processing
              - delivered
              - failed
          example: failed
        - name: since_id
          in: query
          required: false
          description: Send up to 50 stored events with an ID greater than this value before streaming live events. Must be an integer.
          schema:
            type: integer
            format: int64
          example: 4500
        - name: before_id
          in: query
          required: false
          description: Accepted for filter parity with the history route; must be an integer when sent.
          schema:
            type: integer
            format: int64
          example: 4600
        - name: since_time
          in: query
          required: false
          description: Only events created at or after this RFC 3339 time.
          schema:
            type: string
            format: date-time
          example: "2026-09-15T12:00:00Z"
        - name: limit
          in: query
          required: false
          description: Accepted for filter parity with the history route; must be an integer when sent. The catch-up size is fixed at 50.
          schema:
            type: integer
          example: 50
        - name: only_failed
          in: query
          required: false
          description: "`true` forwards only failed events. Any other value is ignored."
          schema:
            type: boolean
          example: true
      responses:
        "200":
          description: SSE stream (`text/event-stream`). Frames repeat until the client disconnects.
          content:
            text/event-stream:
              schema:
                type: string
                description: SSE frames; each `data` line is a JSON-encoded MonitoringEvent.
              example: |
                id: 4521
                event: Message
                data: {"id":4521,"user_id":"bec45bb93cbd24cbec32941ec3c93a12","event_type":"Message","payload":{"type":"Message","instanceName":"store-support"},"status":"pending","attempts":0,"created_at":"2026-09-15T12:05:01Z","updated_at":"2026-09-15T12:05:01Z"}

                :heartbeat

                id: 4522
                event: ReadReceipt
                data: {"id":4522,"user_id":"bec45bb93cbd24cbec32941ec3c93a12","event_type":"ReadReceipt","payload":{"type":"ReadReceipt","instanceName":"store-support"},"status":"pending","attempts":0,"created_at":"2026-09-15T12:05:07Z","updated_at":"2026-09-15T12:05:07Z"}
        "400":
          description: "Bad Request: a numeric or time filter could not be parsed (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: 'Invalid filter: invalid since_id: strconv.ParseInt: parsing "abc": invalid syntax'
        "401":
          description: "Unauthorized: unknown or expired session token, or missing/invalid admin token (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Unauthorized
        "403":
          description: "Forbidden: a non-admin session without a user scope (plain text). Sessions created through this API always carry admin rights, so this is not expected in practice."
          content:
            text/plain:
              schema:
                type: string
              example: User token requires user_id filter
        "503":
          description: "Service Unavailable: monitoring is disabled or the SSE client limit is reached (plain text)."
          content:
            text/plain:
              schema:
                type: string
              examples:
                disabled:
                  summary: Monitoring disabled
                  value: Event monitoring is disabled
                client_limit:
                  summary: Client limit reached
                  value: "Failed to add client: max clients limit reached (100)"
  /admin/events/history:
    get:
      tags:
        - Admin Monitoring
      summary: Query stored monitoring events
      description: |
        Returns stored monitoring events, newest first, with the same filters as the live stream. Use it to fill a dashboard before opening the stream or to page back through older events.

        **Authentication:**
        - `session` query parameter (checked first) or `Authorization: Bearer <admin token>`. User instance tokens are not accepted.
        - Registered without the admin middleware: the page is a raw JSON object (no envelope) and failures are plain-text bodies.

        **Rules:**
        - Results are ordered by `created_at` descending. `limit` defaults to 50 and is capped at 1000.
        - To load the next (older) page, pass `next_id` as `before_id`. `before_id` wins over `since_id` when both are sent.
        - `has_more` is `true` whenever the page is full, even if no older event exists.
        - Credentials are checked before the filters, so a request without credentials returns **401** first.
        - Requires monitoring storage (`MONITORING_STORAGE_ENABLED=true`); otherwise the query fails with **500**.
      security:
        - AdminAuth: []
      parameters:
        - name: session
          in: query
          required: false
          description: Monitoring session token from `POST /admin/events/session`.
          schema:
            type: string
          example: session_1757937600123456789_9f8e7d6c5b4a3210
        - name: user_id
          in: query
          required: false
          description: Only events of this user ID.
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
        - name: event_type
          in: query
          required: false
          description: Only events of this event type (exact, case-sensitive).
          schema:
            type: string
          example: Message
        - name: status
          in: query
          required: false
          description: Only events with this processing status.
          schema:
            type: string
            enum:
              - pending
              - processing
              - delivered
              - failed
          example: delivered
        - name: since_id
          in: query
          required: false
          description: Only events with an ID greater than this value (ignored when `before_id` is sent).
          schema:
            type: integer
            format: int64
          example: 4400
        - name: before_id
          in: query
          required: false
          description: Only events with an ID lower than this value; use the previous page's `next_id`.
          schema:
            type: integer
            format: int64
          example: 4522
        - name: since_time
          in: query
          required: false
          description: Only events created at or after this RFC 3339 time.
          schema:
            type: string
            format: date-time
          example: "2026-09-15T00:00:00Z"
        - name: limit
          in: query
          required: false
          description: Page size; 0 or omitted uses 50, values above 1000 are capped.
          schema:
            type: integer
            default: 50
            maximum: 1000
          example: 100
        - name: only_failed
          in: query
          required: false
          description: "`true` returns only failed events (when `status` is not sent)."
          schema:
            type: boolean
          example: false
      responses:
        "200":
          description: One page of stored events (raw object, no envelope).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminMonitoringHistory"
              example:
                events:
                  - id: 4522
                    user_id: bec45bb93cbd24cbec32941ec3c93a12
                    event_type: ReadReceipt
                    payload:
                      type: ReadReceipt
                      instanceName: store-support
                    status: delivered
                    attempts: 1
                    created_at: "2026-09-15T12:05:07Z"
                    updated_at: "2026-09-15T12:05:08Z"
                  - id: 4521
                    user_id: bec45bb93cbd24cbec32941ec3c93a12
                    event_type: Message
                    payload:
                      type: Message
                      instanceName: store-support
                    status: delivered
                    attempts: 1
                    created_at: "2026-09-15T12:05:01Z"
                    updated_at: "2026-09-15T12:05:02Z"
                has_more: true
                next_id: 4521
                count: 2
        "400":
          description: "Bad Request: a numeric or time filter could not be parsed (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: 'Invalid filter: invalid limit: strconv.Atoi: parsing "ten": invalid syntax'
        "401":
          description: "Unauthorized: unknown or expired session token, or missing/invalid admin token (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Unauthorized
        "500":
          description: "Internal Server Error: the events query failed (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Failed to query events
        "503":
          description: "Service Unavailable: monitoring is disabled (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Event monitoring is disabled
  /admin/events/stats:
    get:
      tags:
        - Admin Monitoring
      summary: Get monitoring system statistics
      description: |
        Returns the state of the monitoring subsystem: SSE hub counters, notifier queue usage, stored event counts by status and type, and every connected SSE client.

        **Features:**
        - `events.by_type` lists the 10 most frequent stored event types.
        - `clients` includes each client's session token, scope, last delivered event and filter.

        **Rules:**
        - Only `Authorization: Bearer <admin token>` is accepted; session tokens are not.
        - The handler writes the statistics object directly (no `{code, data, success}` envelope); failures are plain text.
        - Count queries that fail are returned as empty objects instead of an error.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Monitoring statistics (raw object, no envelope).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MonitoringStatistics"
              example:
                hub:
                  connected_clients: 1
                  total_events_broadcast: 1284
                  total_events_delivered: 1279
                  cache_hits: 542
                  cache_misses: 38
                  cache_hit_rate: 0.9345
                  uptime_seconds: 86400
                  start_time: "2026-09-14T12:00:00Z"
                  last_broadcast_at: "2026-09-15T12:05:07Z"
                notifier:
                  enabled: true
                  workers: 5
                  queue_size: 2
                  queue_capacity: 10000
                  queue_utilization: 0.0002
                events:
                  by_status:
                    delivered: 1200
                    failed: 54
                    pending: 30
                  by_type:
                    Message: 890
                    ReadReceipt: 320
                clients:
                  - id: client_1757937600123456789_ab12cd34
                    session_token: session_1757937600123456789_9f8e7d6c5b4a3210
                    user_id: ""
                    is_admin: true
                    connected: true
                    connected_at: "2026-09-15T12:00:00Z"
                    last_event_id: 4522
                    last_heartbeat: "2026-09-15T12:00:00Z"
                    connection_time: 307.2
                    filter:
                      event_type: Message
                      limit: 50
        "401":
          description: "Unauthorized: missing or invalid admin token (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Unauthorized
        "503":
          description: "Service Unavailable: monitoring is disabled (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Event monitoring is disabled
  /admin/events/prune:
    post:
      tags:
        - Admin Monitoring
      summary: Prune stored monitoring events
      description: |
        Deletes stored monitoring events older than the retention window right away, instead of waiting for the scheduled pruner.

        **Rules:**
        - Only `Authorization: Bearer <admin token>` is accepted.
        - The body is optional. `retention_hours` greater than 0 overrides the window for this call; otherwise the pruner retention (`MONITORING_RETENTION_DAYS`, default 7 days) is used.
        - **400** when monitoring storage is disabled or the body is not valid JSON.
        - The result is written as a bare JSON object; errors come from `http.Error` as plain text.
      security:
        - AdminAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminMonitoringPruneRequest"
            examples:
              last_48_hours:
                summary: Keep only the last 48 hours
                description: Deletes every stored monitoring event created more than 48 hours ago.
                value:
                  retention_hours: 48
              configured_retention:
                summary: Use the configured retention
                description: An empty object (or no body) applies the pruner retention window.
                value: {}
      responses:
        "200":
          description: Prune executed (raw object, no envelope).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminMonitoringPruneResult"
              example:
                deleted: 124
                retention_h: 48
                threshold: "2026-09-13T12:00:00.000000001Z"
        "400":
          description: "Bad Request: monitoring storage is disabled, or the body is not valid JSON (plain text)."
          content:
            text/plain:
              schema:
                type: string
              examples:
                storage_disabled:
                  summary: Monitoring storage disabled
                  value: Monitoring storage disabled
                invalid_body:
                  summary: Body is not valid JSON
                  value: Invalid body
        "401":
          description: "Unauthorized: missing or invalid admin token (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Unauthorized
        "500":
          description: "Internal Server Error: the delete statement failed (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Failed to prune
        "503":
          description: "Service Unavailable: monitoring is disabled (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Event monitoring is disabled
  /admin/events/session:
    post:
      tags:
        - Admin Monitoring
      summary: Create a monitoring session token
      description: |
        Issues a short-lived session token for the monitoring routes, so a browser `EventSource` (which cannot send headers) can open `/admin/events/stream` without exposing the admin token.

        **Features:**
        - `stream_url` is an absolute URL with `session` and the requested filters as query parameters. Its base comes from `MONITORING_STREAM_BASE_URL`, then `ZUCKZAPGO_ADDRESS`, then `http://localhost:<ZUCKZAPGO_PORT>`.
        - The token is valid for 5 minutes (`ttl_seconds: 300`) and can be used on the stream and history routes.

        **Rules:**
        - Authenticate with `Authorization: Bearer <admin token>` or an existing `session` query token (renewal).
        - The body is optional; an invalid or missing body is treated as no filters.
        - Filters only take effect through the query string of `stream_url`; the session itself grants admin scope.
        - The token object is returned without the JSON envelope, and errors are plain-text bodies.
      security:
        - AdminAuth: []
      parameters:
        - name: session
          in: query
          required: false
          description: Existing monitoring session token, to renew without the admin token.
          schema:
            type: string
          example: session_1757937600123456789_9f8e7d6c5b4a3210
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MonitoringSessionTokenRequest"
            examples:
              failed_messages_for_user:
                summary: Failed Message events of one user
                description: The returned stream URL carries `user_id`, `event_type` and `only_failed`.
                value:
                  filters:
                    user_id: bec45bb93cbd24cbec32941ec3c93a12
                    event_type: Message
                    only_failed: true
              everything:
                summary: No filters
                description: The stream URL only carries the session token, so every event is streamed.
                value: {}
      responses:
        "200":
          description: Session token created (raw object, no envelope).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MonitoringSessionToken"
              example:
                session_token: session_1757937600123456789_9f8e7d6c5b4a3210
                expires_at: "2026-09-15T12:05:00.123456789Z"
                stream_url: https://api.example.com/admin/events/stream?event_type=Message&only_failed=true&session=session_1757937600123456789_9f8e7d6c5b4a3210&user_id=bec45bb93cbd24cbec32941ec3c93a12
                ttl_seconds: 300
        "401":
          description: "Unauthorized: unknown or expired session token, or missing/invalid admin token (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Unauthorized
        "503":
          description: "Service Unavailable: monitoring is disabled (plain text)."
          content:
            text/plain:
              schema:
                type: string
              example: Event monitoring is disabled

  # Dead Letter Queue (DLQ) Management Endpoints
  /admin/dlq:
    get:
      tags:
        - Admin DLQ
      summary: List Dead Letter Queue events
      description: |
        Lists events that exhausted their delivery attempts on a transport and were moved to the Dead Letter Queue (archive rows with status `dlq`), newest failure first.

        **Features:**
        - Filter by `transport`, `user_id`, `event_type` and a `failed_at` window (`from_date` / `to_date`).
        - Paginate with `limit` (1-1000, default 50) and `offset`.
        - The response echoes the applied filter at the top level together with `count`.

        **Rules:**
        - Dates must be RFC 3339 (for example `2026-09-15T10:30:00Z`); an invalid date, limit or offset returns **400** with `details`.
        - `payload` is the original event envelope as a JSON string and includes the user's token; treat it as sensitive.
        - The echoed `filter` uses the Go field names (`Transport`, `UserID`, ...), with zero dates when no window was sent.
        - **500** `buffer system not available` when the dispatcher has no buffer; with the in-memory buffer (which has no DLQ) the query fails with **500** `failed to retrieve DLQ events`.
      security:
        - AdminAuth: []
      parameters:
        - name: transport
          in: query
          required: false
          description: Transport that failed the delivery.
          schema:
            type: string
            enum:
              - global-webhook
              - global-rabbitmq
              - global-sqs
              - global-redis
              - global-websocket
          example: global-webhook
        - name: user_id
          in: query
          required: false
          description: Only events of this user ID.
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
        - name: event_type
          in: query
          required: false
          description: Only events of this event type (exact, case-sensitive).
          schema:
            type: string
          example: Message
        - name: from_date
          in: query
          required: false
          description: Only events that failed at or after this RFC 3339 time.
          schema:
            type: string
            format: date-time
          example: "2026-09-01T00:00:00Z"
        - name: to_date
          in: query
          required: false
          description: Only events that failed at or before this RFC 3339 time.
          schema:
            type: string
            format: date-time
          example: "2026-09-15T23:59:59Z"
        - name: limit
          in: query
          required: false
          description: Page size, 1 to 1000.
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 50
          example: 50
        - name: offset
          in: query
          required: false
          description: Number of events to skip.
          schema:
            type: integer
            minimum: 0
            default: 0
          example: 0
      responses:
        "200":
          description: DLQ page with the applied filter.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQListResponse"
              example:
                code: 200
                count: 1
                data:
                  - id: 1234
                    transport: global-webhook
                    user_id: bec45bb93cbd24cbec32941ec3c93a12
                    event_type: Message
                    payload: '{"id":"evt_01J8Z6","userID":"bec45bb93cbd24cbec32941ec3c93a12","userToken":"a1b2c3d4e5f6a7b8c9d0","eventType":"Message","payload":{"type":"Message"},"createdAt":"2026-09-15T10:29:58Z"}'
                    attempts: 12
                    last_error: "webhook returned status 503"
                    failed_at: "2026-09-15T10:30:00Z"
                    archived_at: "2026-09-15T10:30:00Z"
                    replay_count: 0
                filter:
                  Transport: global-webhook
                  UserID: ""
                  EventType: ""
                  FromDate: "0001-01-01T00:00:00Z"
                  ToDate: "0001-01-01T00:00:00Z"
                  Limit: 50
                  Offset: 0
                success: true
        "400":
          description: "Bad Request: `from_date`, `to_date`, `limit` or `offset` is invalid."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                invalid_from_date:
                  summary: from_date is not RFC 3339
                  value:
                    code: 400
                    details: from_date must be in RFC3339 format (e.g., 2024-01-15T10:30:00Z)
                    error: invalid from_date format
                    success: false
                invalid_limit:
                  summary: limit outside 1-1000
                  value:
                    code: 400
                    details: limit must be a number between 1 and 1000
                    error: invalid limit parameter
                    success: false
                invalid_offset:
                  summary: offset is negative
                  value:
                    code: 400
                    details: offset must be a non-negative number
                    error: invalid offset parameter
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the DLQ query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                query_failed:
                  summary: DLQ query failed
                  value:
                    code: 500
                    error: failed to retrieve DLQ events
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    delete:
      tags:
        - Admin DLQ
      summary: Bulk delete Dead Letter Queue events
      description: |
        Permanently deletes DLQ events that match a filter, up to `limit` events per call. Use it to discard failures that will never be replayed, for example after removing a user or fixing a misconfigured transport.

        **Rules:**
        - At least one of `transport`, `user_id` or `event_type` is required (**400** otherwise), so the whole DLQ cannot be wiped by accident.
        - `limit` defaults to 50 and is capped at 1000; repeat the call until `deleted` is 0 to clear larger sets.
        - Events are deleted one by one. When any deletion fails the response is still **200** with top-level `success: false`, `failed` and an `errors` list.
        - No matching event returns **200** with `deleted: 0`.
        - The operation is irreversible.
      security:
        - AdminAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminDLQBulkDeleteRequest"
            examples:
              by_transport:
                summary: Every failure of one transport
                description: Deletes up to 1000 DLQ events that failed on the global webhook.
                value:
                  transport: global-webhook
                  limit: 1000
              by_user_and_type:
                summary: One user's failed Receipt events
                description: Combines filters; only events matching all of them are deleted (default limit 50).
                value:
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  event_type: Receipt
      responses:
        "200":
          description: Bulk deletion finished. Check top-level `success` and `failed` for partial failures.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQDeleteResponse"
              examples:
                deleted:
                  summary: All matching events deleted
                  value:
                    code: 200
                    deleted: 42
                    failed: 0
                    filter:
                      Transport: global-webhook
                      UserID: ""
                      EventType: ""
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 1000
                      Offset: 0
                    success: true
                    total: 42
                nothing_matched:
                  summary: No matching event
                  value:
                    code: 200
                    deleted: 0
                    filter:
                      Transport: ""
                      UserID: bec45bb93cbd24cbec32941ec3c93a12
                      EventType: Receipt
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 50
                      Offset: 0
                    message: No DLQ events found matching filter
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or no filter was sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                invalid_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid request payload for bulk deletion
                    success: false
                missing_filter:
                  summary: No transport, user_id or event_type
                  value:
                    code: 400
                    details: To delete all DLQ events, please use the admin interface with confirmation
                    error: bulk deletion requires at least one filter (transport, user_id, or event_type)
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the matching events could not be listed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                list_failed:
                  summary: Matching events could not be listed
                  value:
                    code: 500
                    error: failed to retrieve DLQ events
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/dlq/stats:
    get:
      tags:
        - Admin DLQ
      summary: Get Dead Letter Queue statistics
      description: |
        Returns how many events are in the Dead Letter Queue, the time of the oldest and newest failure, and counts per transport and per event type. Use it to spot a failing transport before listing individual events.

        **Rules:**
        - `oldest_event` and `newest_event` are omitted when the DLQ is empty.
        - Only archive rows with status `dlq` are counted; delivered events are in `GET /admin/archive/stats`.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: DLQ statistics.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQStatsResponse"
              example:
                code: 200
                data:
                  total_count: 57
                  oldest_event: "2026-09-01T08:12:44Z"
                  newest_event: "2026-09-15T10:30:00Z"
                  by_transport:
                    global-webhook: 42
                    global-rabbitmq: 15
                  by_event_type:
                    Message: 40
                    Receipt: 17
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the statistics query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                stats_failed:
                  summary: Statistics query failed
                  value:
                    code: 500
                    error: failed to retrieve DLQ statistics
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/dlq/{id}:
    get:
      tags:
        - Admin DLQ
      summary: Get a Dead Letter Queue event
      description: |
        Returns one DLQ event by ID, including the original event envelope, the number of attempts, the last delivery error and its replay history. Inspect it before replaying or deleting.

        **Rules:**
        - A non-numeric `id` returns **400** `invalid event ID` with `details`.
        - Only rows with status `dlq` are found; the ID of a delivered archive entry returns **404** (use `GET /admin/archive/{id}`).
        - `payload` includes the user's token; treat it as sensitive.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: DLQ event ID (archive row ID).
          schema:
            type: integer
            format: int64
          example: 1234
      responses:
        "200":
          description: The DLQ event.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQEventResponse"
              example:
                code: 200
                data:
                  id: 1234
                  transport: global-webhook
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  event_type: Message
                  payload: '{"id":"evt_01J8Z6","userID":"bec45bb93cbd24cbec32941ec3c93a12","userToken":"a1b2c3d4e5f6a7b8c9d0","eventType":"Message","payload":{"type":"Message"},"createdAt":"2026-09-15T10:29:58Z"}'
                  attempts: 12
                  last_error: "webhook returned status 503"
                  failed_at: "2026-09-15T10:30:00Z"
                  archived_at: "2026-09-15T10:30:00Z"
                  replay_count: 1
                  last_replay_at: "2026-09-15T11:00:00Z"
                success: true
        "400":
          description: "Bad Request: the path ID is not an integer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 400
                details: event ID must be a valid number
                error: invalid event ID
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no DLQ event with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 404
                details: No DLQ event found with the specified ID
                error: DLQ event not found
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the lookup failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                lookup_failed:
                  summary: Lookup failed
                  value:
                    code: 500
                    error: failed to retrieve DLQ event
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    delete:
      tags:
        - Admin DLQ
      summary: Delete a Dead Letter Queue event
      description: |
        Permanently deletes one DLQ event by ID. Use it to discard a single failure that should never be delivered.

        **Rules:**
        - `id` is parsed as a 64-bit integer; anything else is rejected with **400**.
        - Only rows with status `dlq` are deleted; the ID of a delivered archive entry returns **404** (use `DELETE /admin/archive/{id}`).
        - No request body is read on this route; bulk deletion is `DELETE /admin/dlq`.
        - The operation is irreversible. Consider `POST /admin/dlq/replay` first when the event should still be delivered.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: DLQ event ID (archive row ID).
          schema:
            type: integer
            format: int64
          example: 1234
      responses:
        "200":
          description: DLQ event deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQDeleteResponse"
              example:
                code: 200
                deleted: 1
                event_id: 1234
                message: Event deleted successfully
                success: true
        "400":
          description: "Bad Request: the path ID is not an integer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 400
                details: event ID must be a valid number
                error: invalid event ID
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no DLQ event with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: DLQ event not found
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the delete failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                delete_failed:
                  summary: Delete statement failed
                  value:
                    code: 500
                    error: failed to delete DLQ event
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/dlq/replay:
    post:
      tags:
        - Admin DLQ
      summary: Replay Dead Letter Queue events
      description: |
        Re-enqueues DLQ events on the transport that failed them, either one event by `event_id` or up to `limit` events matching a filter. Use `dry_run` to preview what would be replayed.

        **Features:**
        - Single mode (`event_id` present): replays that event; filters are ignored.
        - Bulk mode (no `event_id`): replays the newest matching failures, `limit` defaulting to 50 and capped at 1000. An empty filter matches the whole DLQ.
        - `dry_run: true` returns the event(s) that would be replayed with `replayed: 0` and changes nothing.

        **Rules:**
        - A replayed event stays in the DLQ with `replay_count` incremented, so repeating a bulk replay enqueues the same events again. Delete them once the delivery succeeds.
        - In bulk mode each event is replayed independently; failures keep **200** with top-level `success: false`, `failed` and `errors`.
        - A single event whose stored envelope cannot be decoded or re-enqueued returns **500**.
      security:
        - AdminAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminDLQReplayRequest"
            examples:
              single:
                summary: Replay one event
                description: Re-enqueues DLQ event 1234 on its original transport.
                value:
                  event_id: 1234
              single_dry_run:
                summary: Preview one event
                description: Returns event 1234 without re-enqueuing it.
                value:
                  event_id: 1234
                  dry_run: true
              bulk_by_transport:
                summary: Replay a transport's failures
                description: Re-enqueues up to 200 webhook failures after the webhook endpoint was fixed.
                value:
                  transport: global-webhook
                  limit: 200
              bulk_dry_run:
                summary: Preview a bulk replay
                description: Lists up to 50 failed Message events of one user that would be replayed.
                value:
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  event_type: Message
                  dry_run: true
      responses:
        "200":
          description: Replay (or preview) finished. In bulk mode check top-level `success` and `failed`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQReplayResponse"
              examples:
                single_replayed:
                  summary: One event replayed
                  value:
                    code: 200
                    event_id: 1234
                    message: Event replayed successfully
                    replayed: 1
                    success: true
                single_dry_run:
                  summary: Preview of one event
                  value:
                    code: 200
                    dry_run: true
                    event:
                      id: 1234
                      transport: global-webhook
                      user_id: bec45bb93cbd24cbec32941ec3c93a12
                      event_type: Message
                      payload: '{"id":"evt_01J8Z6","eventType":"Message"}'
                      attempts: 12
                      last_error: "webhook returned status 503"
                      failed_at: "2026-09-15T10:30:00Z"
                      archived_at: "2026-09-15T10:30:00Z"
                      replay_count: 0
                    message: Dry run - event would be replayed
                    replayed: 0
                    success: true
                bulk_partial_failure:
                  summary: Bulk replay with one failure
                  value:
                    code: 200
                    errors:
                      - "Event ID 1201: failed to unmarshal envelope: unexpected end of JSON input"
                    failed: 1
                    filter:
                      Transport: global-webhook
                      UserID: ""
                      EventType: ""
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 200
                      Offset: 0
                    replayed: 41
                    success: false
                    total: 42
                bulk_nothing_matched:
                  summary: No matching event
                  value:
                    code: 200
                    filter:
                      Transport: global-sqs
                      UserID: ""
                      EventType: ""
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 50
                      Offset: 0
                    message: No DLQ events found matching filter
                    replayed: 0
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: invalid request payload
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: `event_id` is not a DLQ event (single mode)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: DLQ event not found
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, the event(s) could not be read, or a single replay failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                replay_failed:
                  summary: Single replay failed
                  value:
                    code: 500
                    error: failed to replay DLQ event
                    success: false
                list_failed:
                  summary: Bulk events could not be listed
                  value:
                    code: 500
                    error: failed to retrieve DLQ events
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/dlq/prune:
    post:
      tags:
        - Admin DLQ
      summary: Run the buffer pruner now
      description: |
        Runs the persistent buffer pruner immediately instead of waiting for its schedule: completed buffer rows older than the buffer retention and archive rows (delivered and DLQ) older than the archive retention are deleted.

        **Rules:**
        - Nothing newer than the configured retentions is removed; this call does not accept a custom cutoff.
        - **501** when the active buffer implementation has no manual prune; **500** when the pruner is not running or a prune step fails.
        - `pruner_stats` (running state, retentions, cumulative totals, last and next run) is included when the buffer exposes it.
        - No request body is read.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Prune completed. The envelope carries `message` and `pruner_stats`, no `data` key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDLQPruneResponse"
              example:
                code: 200
                message: prune triggered successfully
                pruner_stats:
                  running: true
                  retention_days: 7
                  archive_retention: 30
                  total_pruned: 182340
                  total_archive_pruned: 52000
                  last_prune_time: "2026-09-15T12:00:03Z"
                  next_prune_time: "2026-09-16T00:00:00Z"
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the prune failed (including a stopped pruner)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                prune_failed:
                  summary: Prune failed
                  value:
                    code: 500
                    error: failed to execute prune
                    success: false
        "501":
          description: "Not Implemented: the active buffer has no manual prune."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 501
                error: manual prune not supported for current buffer
                success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/archive:
    get:
      tags:
        - Admin Event Archive
      summary: List archived events
      description: |
        Lists the event archive, which keeps a copy of every event after its delivery finished on a transport: successfully delivered events (`status: success`) and Dead Letter Queue failures (`status: dlq`), newest first.

        **Features:**
        - Filter by `status`, `transport`, `user_id`, `event_type` and a `delivered_at` window (`from_date` / `to_date`).
        - Paginate with `limit` (1-1000, default 50) and `offset`.
        - The response echoes the applied filter at the top level together with `count`.

        **Rules:**
        - `status` accepts only `dlq` or `success`; omit it for both. Invalid status, dates (RFC 3339), limit or offset return **400** with `details`.
        - `payload` is the original event envelope as a JSON string and includes the user's token; treat it as sensitive.
        - The echoed `filter` uses the Go field names (`Status`, `Transport`, ...), with zero dates when no window was sent.
        - Archive rows older than the archive retention are removed by the buffer pruner.
      security:
        - AdminAuth: []
      parameters:
        - name: status
          in: query
          required: false
          description: "`success` for delivered events, `dlq` for failures; omit for both."
          schema:
            type: string
            enum:
              - dlq
              - success
          example: success
        - name: transport
          in: query
          required: false
          description: Transport that handled the event.
          schema:
            type: string
            enum:
              - global-webhook
              - global-rabbitmq
              - global-sqs
              - global-redis
              - global-websocket
          example: global-rabbitmq
        - name: user_id
          in: query
          required: false
          description: Only events of this user ID.
          schema:
            type: string
          example: bec45bb93cbd24cbec32941ec3c93a12
        - name: event_type
          in: query
          required: false
          description: Only events of this event type (exact, case-sensitive).
          schema:
            type: string
          example: Message
        - name: from_date
          in: query
          required: false
          description: Only events archived at or after this RFC 3339 time (`delivered_at`).
          schema:
            type: string
            format: date-time
          example: "2026-09-15T00:00:00Z"
        - name: to_date
          in: query
          required: false
          description: Only events archived at or before this RFC 3339 time (`delivered_at`).
          schema:
            type: string
            format: date-time
          example: "2026-09-15T23:59:59Z"
        - name: limit
          in: query
          required: false
          description: Page size, 1 to 1000.
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 50
          example: 100
        - name: offset
          in: query
          required: false
          description: Number of archived events to skip.
          schema:
            type: integer
            minimum: 0
            default: 0
          example: 100
      responses:
        "200":
          description: Archive page with the applied filter.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminArchiveListResponse"
              example:
                code: 200
                count: 2
                data:
                  - id: 99810
                    transport: global-rabbitmq
                    user_id: bec45bb93cbd24cbec32941ec3c93a12
                    event_type: Message
                    payload: '{"id":"evt_01J8Z9","userID":"bec45bb93cbd24cbec32941ec3c93a12","userToken":"a1b2c3d4e5f6a7b8c9d0","eventType":"Message","payload":{"type":"Message"},"createdAt":"2026-09-15T12:00:00Z"}'
                    status: success
                    attempts: 1
                    created_at: "2026-09-15T12:00:00Z"
                    delivered_at: "2026-09-15T12:00:01Z"
                    replay_count: 0
                  - id: 1234
                    transport: global-webhook
                    user_id: bec45bb93cbd24cbec32941ec3c93a12
                    event_type: Message
                    payload: '{"id":"evt_01J8Z6","userID":"bec45bb93cbd24cbec32941ec3c93a12","userToken":"a1b2c3d4e5f6a7b8c9d0","eventType":"Message","payload":{"type":"Message"},"createdAt":"2026-09-15T10:29:58Z"}'
                    status: dlq
                    attempts: 12
                    last_error: webhook returned status 503
                    failed_at: "2026-09-15T10:30:00Z"
                    created_at: "2026-09-15T10:29:58Z"
                    delivered_at: "2026-09-15T10:30:00Z"
                    replay_count: 0
                filter:
                  Status: ""
                  Transport: ""
                  UserID: bec45bb93cbd24cbec32941ec3c93a12
                  EventType: Message
                  FromDate: "0001-01-01T00:00:00Z"
                  ToDate: "0001-01-01T00:00:00Z"
                  Limit: 50
                  Offset: 0
                success: true
        "400":
          description: "Bad Request: `status`, `from_date`, `to_date`, `limit` or `offset` is invalid."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                invalid_status:
                  summary: status is not dlq or success
                  value:
                    code: 400
                    details: status must be 'dlq', 'success', or empty for all
                    error: invalid status filter
                    success: false
                invalid_to_date:
                  summary: to_date is not RFC 3339
                  value:
                    code: 400
                    details: to_date must be in RFC3339 format (e.g., 2024-01-15T10:30:00Z)
                    error: invalid to_date format
                    success: false
                invalid_limit:
                  summary: limit outside 1-1000
                  value:
                    code: 400
                    details: limit must be a number between 1 and 1000
                    error: invalid limit parameter
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the dispatcher has no buffer, or the archive query failed (always the case with the in-memory buffer, which has no archive)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                query_failed:
                  summary: Archive query failed
                  value:
                    code: 500
                    error: failed to retrieve archive events
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    delete:
      tags:
        - Admin Event Archive
      summary: Bulk delete archived events
      description: |
        Permanently deletes archived events (delivered, failed or both) that match a filter, up to `limit` rows per call. Use it to shrink the archive before the scheduled pruner runs, for example for a removed user.

        **Rules:**
        - At least one of `status`, `transport`, `user_id` or `event_type` is required (**400** otherwise).
        - `status` accepts only `dlq` or `success`.
        - `limit` defaults to 50 and is capped at 1000; repeat the call until `deleted` is 0 to clear larger sets.
        - Rows are deleted one by one. When any deletion fails the response is still **200** with top-level `success: false`, `failed` and an `errors` list.
        - The operation is irreversible.
      security:
        - AdminAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteBulkRequest"
            examples:
              delivered_of_user:
                summary: Delivered events of one user
                description: Deletes up to 1000 successfully delivered archive rows of a user.
                value:
                  status: success
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  limit: 1000
              dlq_of_transport:
                summary: Failures of one transport
                description: Deletes up to 50 DLQ rows of the global RabbitMQ transport.
                value:
                  status: dlq
                  transport: global-rabbitmq
      responses:
        "200":
          description: Bulk deletion finished. Check top-level `success` and `failed` for partial failures.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminArchiveDeleteResponse"
              examples:
                deleted:
                  summary: All matching rows deleted
                  value:
                    code: 200
                    deleted: 1000
                    failed: 0
                    filter:
                      Status: success
                      Transport: ""
                      UserID: bec45bb93cbd24cbec32941ec3c93a12
                      EventType: ""
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 1000
                      Offset: 0
                    success: true
                    total: 1000
                nothing_matched:
                  summary: No matching row
                  value:
                    code: 200
                    deleted: 0
                    filter:
                      Status: dlq
                      Transport: global-rabbitmq
                      UserID: ""
                      EventType: ""
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 50
                      Offset: 0
                    message: No archive events found matching filter
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, no filter was sent, or `status` is invalid."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                invalid_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid request payload for bulk deletion
                    success: false
                missing_filter:
                  summary: No filter sent
                  value:
                    code: 400
                    details: To delete all archive events, please use the admin interface with confirmation
                    error: bulk deletion requires at least one filter (status, transport, user_id, or event_type)
                    success: false
                invalid_status:
                  summary: status is not dlq or success
                  value:
                    code: 400
                    details: status must be 'dlq', 'success', or empty for all
                    error: invalid status filter
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the matching rows could not be listed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                list_failed:
                  summary: Matching rows could not be listed
                  value:
                    code: 500
                    error: failed to retrieve archive events
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/archive/stats:
    get:
      tags:
        - Admin Event Archive
      summary: Get event archive statistics
      description: |
        Returns archive totals split by status (delivered vs DLQ), the oldest and newest row of each status, and counts per transport, event type, status and transport-status pair. Use it to watch delivery health over time.

        **Rules:**
        - Oldest/newest timestamps are omitted for a status with no rows.
        - Success timestamps use `delivered_at`; DLQ timestamps use `failed_at`.
      security:
        - AdminAuth: []
      responses:
        "200":
          description: Archive statistics.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminArchiveStatsResponse"
              example:
                code: 200
                data:
                  total_count: 99805
                  success_count: 99748
                  dlq_count: 57
                  oldest_success: "2026-08-16T00:00:02Z"
                  newest_success: "2026-09-15T12:00:01Z"
                  oldest_dlq: "2026-09-01T08:12:44Z"
                  newest_dlq: "2026-09-15T10:30:00Z"
                  by_transport:
                    global-webhook: 50012
                    global-rabbitmq: 49793
                  by_event_type:
                    Message: 70100
                    ReadReceipt: 29705
                  by_status:
                    success: 99748
                    dlq: 57
                  by_transport_status:
                    global-webhook:
                      success: 49970
                      dlq: 42
                    global-rabbitmq:
                      success: 49778
                      dlq: 15
                success: true
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the statistics query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                stats_failed:
                  summary: Statistics query failed
                  value:
                    code: 500
                    error: failed to retrieve archive statistics
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/archive/{id}:
    get:
      tags:
        - Admin Event Archive
      summary: Get an archived event
      description: |
        Returns one archive row by ID, delivered or failed, with its original envelope, timestamps and replay history.

        **Rules:**
        - The path `id` must be numeric, otherwise **400** `invalid event ID`.
        - Finds rows of both statuses, unlike `GET /admin/dlq/{id}`.
        - `payload` includes the user's token; treat it as sensitive.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Archive row ID.
          schema:
            type: integer
            format: int64
          example: 99810
      responses:
        "200":
          description: The archived event.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminArchiveEventResponse"
              example:
                code: 200
                data:
                  id: 99810
                  transport: global-rabbitmq
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  event_type: Message
                  payload: '{"id":"evt_01J8Z9","userID":"bec45bb93cbd24cbec32941ec3c93a12","userToken":"a1b2c3d4e5f6a7b8c9d0","eventType":"Message","payload":{"type":"Message"},"createdAt":"2026-09-15T12:00:00Z"}'
                  status: success
                  attempts: 1
                  created_at: "2026-09-15T12:00:00Z"
                  delivered_at: "2026-09-15T12:00:01Z"
                  replay_count: 0
                success: true
        "400":
          description: "Bad Request: the path ID is not an integer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 400
                details: event ID must be a valid number
                error: invalid event ID
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no archive row with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 404
                details: No archive event found with the specified ID
                error: archive event not found
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the lookup failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                lookup_failed:
                  summary: Lookup failed
                  value:
                    code: 500
                    error: failed to retrieve archive event
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false
    delete:
      tags:
        - Admin Event Archive
      summary: Delete an archived event
      description: |
        Permanently deletes one archive row by ID, whatever its status. Use it to remove a single copy, for example an event that holds data that must not be retained.

        **Rules:**
        - Send the numeric archive row ID; a non-integer value returns **400** with `details`.
        - Deletes rows of both statuses, unlike `DELETE /admin/dlq/{id}`.
        - No request body is read on this route; bulk deletion is `DELETE /admin/archive`.
        - The operation is irreversible.
      security:
        - AdminAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Archive row ID.
          schema:
            type: integer
            format: int64
          example: 99810
      responses:
        "200":
          description: Archive row deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminArchiveDeleteResponse"
              example:
                code: 200
                deleted: 1
                event_id: 99810
                message: Event deleted successfully
                success: true
        "400":
          description: "Bad Request: the path ID is not an integer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              example:
                code: 400
                details: event ID must be a valid number
                error: invalid event ID
                success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no archive row with this ID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: archive event not found
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, or the delete failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                delete_failed:
                  summary: Delete failed
                  value:
                    code: 500
                    error: failed to delete archive event
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /admin/archive/replay:
    post:
      tags:
        - Admin Event Archive
      summary: Replay archived events
      description: |
        Re-enqueues archived events on their original transport, either one row by `event_id` or up to `limit` rows matching a filter. Unlike the DLQ replay, delivered events can be replayed too, for example to rebuild a consumer that lost data.

        **Features:**
        - Single mode (`event_id` present): replays that row of any status; filters are ignored.
        - Bulk mode (no `event_id`): replays the newest matching rows, `limit` defaulting to 50 and capped at 1000. An empty filter matches the whole archive.
        - `dry_run: true` returns the row(s) that would be replayed with `replayed: 0` and changes nothing.

        **Rules:**
        - Replaying `success` rows delivers those events **again**; consumers must be idempotent.
        - Replayed rows stay in the archive with `replay_count` incremented, so repeating a bulk replay enqueues them again.
        - `status` accepts only `dlq` or `success` (**400** with `details` otherwise).
        - In bulk mode each row is replayed independently; failures keep **200** with top-level `success: false`, `failed` and `errors`.
      security:
        - AdminAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReplayRequest"
            examples:
              single_replay:
                summary: Replay one archived event
                description: Re-enqueues archive row 99810 on its original transport, whatever its status.
                value:
                  event_id: 99810
              rebuild_consumer:
                summary: Re-deliver a user's delivered messages
                description: Re-enqueues up to 1000 delivered Message events of one user on RabbitMQ.
                value:
                  status: success
                  transport: global-rabbitmq
                  user_id: bec45bb93cbd24cbec32941ec3c93a12
                  event_type: Message
                  limit: 1000
              dry_run:
                summary: Preview a bulk replay
                description: Lists up to 50 DLQ rows that would be replayed, without enqueuing anything.
                value:
                  status: dlq
                  dry_run: true
      responses:
        "200":
          description: Replay (or preview) finished. In bulk mode check top-level `success` and `failed`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminArchiveReplayResponse"
              examples:
                single_replayed:
                  summary: One row replayed
                  value:
                    code: 200
                    event_id: 99810
                    message: Event replayed successfully
                    replayed: 1
                    success: true
                bulk_replayed:
                  summary: Bulk replay without failures
                  value:
                    code: 200
                    failed: 0
                    filter:
                      Status: success
                      Transport: global-rabbitmq
                      UserID: bec45bb93cbd24cbec32941ec3c93a12
                      EventType: Message
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 1000
                      Offset: 0
                    replayed: 312
                    success: true
                    total: 312
                bulk_dry_run_empty:
                  summary: Dry run with no matching row
                  value:
                    code: 200
                    count: 0
                    dry_run: true
                    filter:
                      Status: dlq
                      Transport: ""
                      UserID: ""
                      EventType: ""
                      FromDate: "0001-01-01T00:00:00Z"
                      ToDate: "0001-01-01T00:00:00Z"
                      Limit: 50
                      Offset: 0
                    message: No archive events found matching filter
                    replayed: 0
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `status` is invalid."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminErrorWithDetails"
              examples:
                invalid_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid request payload
                    success: false
                invalid_status:
                  summary: status is not dlq or success
                  value:
                    code: 400
                    details: status must be 'dlq', 'success', or empty for all
                    error: invalid status filter
                    success: false
        "401":
          description: Missing or invalid admin token (header `Authorization`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: `event_id` does not exist in the archive (single mode)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: archive event not found
                success: false
        "500":
          description: "Internal Server Error: no persistent buffer, the row(s) could not be read, or a single replay failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                buffer_unavailable:
                  summary: No persistent buffer
                  value:
                    code: 500
                    error: buffer system not available
                    success: false
                replay_failed:
                  summary: Single replay failed
                  value:
                    code: 500
                    error: failed to replay archive event
                    success: false
                list_failed:
                  summary: Bulk rows could not be listed
                  value:
                    code: 500
                    error: failed to retrieve archive events
                    success: false
        "503":
          description: "Service Unavailable: the gateway was started without an admin token."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 503
                error: admin not configured
                success: false

  /status/send/text:
    post:
      tags:
        - Status
      summary: Post a text status
      description: |
        Posts a text status (story) to `status@broadcast`, with optional background color, text color and font. It stays visible to the chosen audience for 24 hours.

        **Rules:**
        - `text` is required. WhatsApp limits status text to about 650 characters; the gateway does not truncate or check the length.
        - `background_color` and `text_color` are ARGB colors written as unsigned decimal strings (for example `4278190080` is opaque black). Values that do not parse are silently ignored.
        - `font` accepts 0-10; `0` (system font) is used when omitted, and values outside the range are ignored.
        - `audience` limits the text status to individual contacts (phone digits, `@s.whatsapp.net` or `@lid`, no devices or groups); `exclude_list` removes contacts. Omit both to follow the account's status privacy.
        - Entries that are not individual contacts are ignored with a warning; an empty or entirely invalid `audience` uses the account's status privacy. A valid `audience` or `exclude_list` is never widened: if it cannot be applied (nobody remains after `exclude_list`, or a contact cannot be resolved) the status is **not** sent and the route returns **500** `failed to send text status: ... (status audience could not be applied; the status was not sent)`. The audience never causes a **400**.
        - `id` sets a custom message ID; otherwise one is generated.

        **Fonts:**
        - `0` SYSTEM, `1` SYSTEM_TEXT, `2` FB_SCRIPT, `6` SYSTEM_BOLD
        - `7` MORNINGBREEZE_REGULAR, `8` CALISTOGA_REGULAR, `9` EXO2_EXTRABOLD, `10` COURIERPRIME_BOLD
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StatusText"
            examples:
              simple:
                summary: Plain text status
                description: Uses the system font and default colors, visible according to the status privacy setting.
                value:
                  text: Store open until 10 PM today!
              styled:
                summary: Colored background and custom font
                description: Opaque blue background, white text and the CALISTOGA_REGULAR font.
                value:
                  text: New collection available now
                  background_color: "4280391411"
                  text_color: "4294967295"
                  font: 8
              restricted_audience:
                summary: Visible only to selected contacts
                description: "`audience` limits who sees the status; `exclude_list` removes one of them."
                value:
                  text: Private update for the team
                  audience:
                    - "5511999999999"
                    - "5511888888888@s.whatsapp.net"
                    - "123456789012345@lid"
                  exclude_list:
                    - "5511888888888"
      responses:
        "200":
          description: The text status was sent and acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSendResponse"
              example:
                code: 200
                data:
                  message_id: 3EB06F9067F80BAB89FF
                  timestamp: "2026-09-15T10:30:00-03:00"
                  status: sent
                  type: text_status
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `text` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_request_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                text_is_required:
                  summary: text missing
                  value:
                    code: 400
                    error: text is required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp client for this user, the client is not connected, or the text status could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp client for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send text status: context deadline exceeded"
                    success: false
  /status/send/image:
    post:
      tags:
        - Status
      summary: Post an image status
      description: |
        Uploads an image and posts it as a status (story) to `status@broadcast`, with an optional caption. It stays visible to the chosen audience for 24 hours.

        **Rules:**
        - `image` is required and accepts a base64 data URL (`data:image/jpeg;base64,...`) or an HTTP(S) URL that the gateway downloads. JPEG and PNG are the safest formats.
        - An image that cannot be decoded or downloaded returns **400** `failed to process image: ...`; an upload failure returns **500**.
        - `audience` restricts the image status to individual contacts (phone digits, `@s.whatsapp.net` or `@lid`) and `exclude_list` removes contacts; without them the account's status privacy applies; entries that are not individual contacts are ignored with a warning; an empty or entirely invalid `audience` uses the account's status privacy. A valid `audience` or `exclude_list` is never widened: if it cannot be applied (nobody remains after `exclude_list`, or a contact cannot be resolved) the status is **not** sent and the route returns **500** `failed to send image status: ... (status audience could not be applied; the status was not sent)`. The audience never causes a **400**.
        - Upload and send share a 60-second timeout. `id` sets a custom message ID.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StatusImage"
            examples:
              from_url:
                summary: Image from a public URL with caption
                description: The gateway downloads the image before uploading it to WhatsApp.
                value:
                  image: https://example.com/media/weekly-offer.jpg
                  caption: This week's offer
              base64_restricted:
                summary: Base64 image for selected contacts
                description: Inline data URL shown only to the listed contacts.
                value:
                  image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
                  caption: Preview for VIP customers
                  audience:
                    - "5511999999999"
                    - "123456789012345@lid"
      responses:
        "200":
          description: The image was uploaded and the status acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSendResponse"
              example:
                code: 200
                data:
                  message_id: 3EB0A3C4D5E6F7A8B9C0
                  timestamp: "2026-09-15T10:31:00-03:00"
                  status: sent
                  type: image_status
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `image` is missing or cannot be processed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_request_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                image_is_required:
                  summary: image missing
                  value:
                    code: 400
                    error: image is required
                    success: false
                invalid_media_input:
                  summary: image is neither a data URL nor an HTTP(S) URL
                  value:
                    code: 400
                    error: "failed to process image: invalid media input: must be either base64 data URL or HTTP(S) URL"
                    success: false
                download_http_error:
                  summary: Image URL returned an HTTP error
                  value:
                    code: 400
                    error: "failed to process image: HTTP error 404 when downloading from URL"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp client, the client is not connected, or the image upload or status send failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp client for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                upload_failed:
                  summary: Upload to WhatsApp failed
                  value:
                    code: 500
                    error: "failed to upload image: context deadline exceeded"
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send image status: websocket not connected"
                    success: false
  /status/send/video:
    post:
      tags:
        - Status
      summary: Post a video status
      description: |
        Uploads a video and posts it as a status (story) to `status@broadcast`, with an optional caption. It stays visible to the chosen audience for 24 hours.

        **Rules:**
        - `video` is required and accepts a base64 data URL (`data:video/mp4;base64,...`) or an HTTP(S) URL. Use MP4 with H.264 video and AAC audio for playback on every device.
        - The duration is read from the MP4 header when possible; WhatsApp limits video statuses to short clips (about 60 seconds).
        - A video that cannot be decoded or downloaded returns **400** `failed to process video: ...`; an upload failure returns **500**.
        - For a video status, `audience` accepts only individual contacts (phone digits, `@s.whatsapp.net` or `@lid`) and `exclude_list` subtracts from it; omitted, the account's status privacy decides; entries that are not individual contacts are ignored with a warning; an empty or entirely invalid `audience` uses the account's status privacy. A valid `audience` or `exclude_list` is never widened: if it cannot be applied (nobody remains after `exclude_list`, or a contact cannot be resolved) the status is **not** sent and the route returns **500** `failed to send video status: ... (status audience could not be applied; the status was not sent)`. The audience never causes a **400**.
        - Upload and send share a 120-second timeout. `id` sets a custom message ID.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StatusVideo"
            examples:
              from_url:
                summary: MP4 from a public URL with caption
                description: The gateway downloads the video before uploading it to WhatsApp.
                value:
                  video: https://example.com/media/product-demo.mp4
                  caption: 30-second product demo
              base64_with_exclusion:
                summary: Base64 video hidden from one contact
                description: No `audience`, so the status privacy applies, minus the excluded contact.
                value:
                  video: data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAu1tZGF0
                  caption: Behind the scenes
                  exclude_list:
                    - "5511888888888"
      responses:
        "200":
          description: The video was uploaded and the status acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSendResponse"
              example:
                code: 200
                data:
                  message_id: 3EB0B4C5D6E7F8A9B0C1
                  timestamp: "2026-09-15T10:32:00-03:00"
                  status: sent
                  type: video_status
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `video` is missing or cannot be processed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_request_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                video_is_required:
                  summary: video missing
                  value:
                    code: 400
                    error: video is required
                    success: false
                invalid_base64:
                  summary: Data URL with invalid base64
                  value:
                    code: 400
                    error: "failed to process video: could not decode base64 data: illegal base64 data at input byte 12"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp client, the client is not connected, or the video upload or status send failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp client for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                upload_failed:
                  summary: Upload to WhatsApp failed
                  value:
                    code: 500
                    error: "failed to upload video: context deadline exceeded"
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send video status: websocket not connected"
                    success: false
  /status/send/audio:
    post:
      tags:
        - Status
      summary: Post an audio or voice note status
      description: |
        Uploads audio and posts it as a status (story) to `status@broadcast`, either as a voice note or as an audio file. A waveform is always generated.

        **Rules:**
        - `audio` is required and accepts a base64 data URL or an HTTP(S) URL.
        - **Voice note (`ptt: true`, default):** send OGG/Opus. The MIME type is normalized to exactly `audio/ogg; codecs=opus`, because the official apps silently drop voice-note statuses with any other MIME type.
        - **Audio file (`ptt: false`):** MP3, M4A/AAC or OGG; unknown types fall back to `audio/mpeg`.
        - Audio statuses honor `audience` (individual phone digits, `@s.whatsapp.net` or `@lid` only) and `exclude_list`; leaving both out uses the account's status privacy; entries that are not individual contacts are ignored with a warning; an empty or entirely invalid `audience` uses the account's status privacy. A valid `audience` or `exclude_list` is never widened: if it cannot be applied (nobody remains after `exclude_list`, or a contact cannot be resolved) the status is **not** sent and the route returns **500** `failed to send audio status: ... (status audience could not be applied; the status was not sent)`. The audience never causes a **400**.
        - The response reports the final `mime_type`, the `duration` in seconds and `has_waveform`. Upload and send share a 60-second timeout.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StatusAudio"
            examples:
              voice_note:
                summary: Voice note status from OGG/Opus
                description: "`ptt` defaults to true, so this is posted as a voice note."
                value:
                  audio: data:audio/ogg;base64,T2dnUwACAAAAAAAAAAAAAAAAAAAAAMhHv8oBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dT
              audio_file_from_url:
                summary: MP3 audio file for selected contacts
                description: "`ptt: false` posts a regular audio file instead of a voice note."
                value:
                  audio: https://example.com/media/jingle.mp3
                  ptt: false
                  audience:
                    - "5511999999999"
      responses:
        "200":
          description: The audio was uploaded and the status acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSendAudioResponse"
              examples:
                voice_note:
                  summary: Voice note posted
                  value:
                    code: 200
                    data:
                      message_id: 3EB072C49492CB69CBCBCF
                      timestamp: "2026-09-14T22:25:15-03:00"
                      status: sent
                      type: voice_status
                      ptt: true
                      duration: 4
                      has_waveform: true
                      mime_type: audio/ogg; codecs=opus
                    success: true
                audio_file:
                  summary: Audio file posted
                  value:
                    code: 200
                    data:
                      message_id: 3EB072C49492CB69CBCBD0
                      timestamp: "2026-09-14T22:26:40-03:00"
                      status: sent
                      type: audio_status
                      ptt: false
                      duration: 32
                      has_waveform: true
                      mime_type: audio/mpeg
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `audio` is missing or cannot be processed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_request_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                audio_is_required:
                  summary: audio missing
                  value:
                    code: 400
                    error: audio is required
                    success: false
                download_failed:
                  summary: Audio URL could not be downloaded
                  value:
                    code: 400
                    error: "failed to process audio: HTTP error 403 when downloading from URL"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp client, the client is not connected, or the audio upload or status send failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp client for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                upload_failed:
                  summary: Upload to WhatsApp failed
                  value:
                    code: 500
                    error: "failed to upload audio: context deadline exceeded"
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send audio status: websocket not connected"
                    success: false
  /call/reject/send:
    post:
      tags:
        - Call
      summary: Reject a call and send a follow-up text (legacy)
      description: |
        Rejects an incoming call through the WhatsApp session and, when `message` is set, sends it to the caller
        as a plain text message. This is the original rejection route; `POST /call/reject` is the newer variant
        with a default `reject_type` and an honest `message_sent` flag.

        **Rules:**
        - `call_id`, `call_from` and `reject_type` are required. `reject_type` has no default here: omitting it returns **400**.
        - `call_from` must be the caller JID exactly as received in the call offer webhook (for example `5511999999999@s.whatsapp.net` or `123456789012345@lid`).
        - The session must exist and be connected; that is checked **before** the body is read, so a disconnected session answers **500** even for an invalid body.
        - `reject_type` is validated and echoed back, but the rejection stanza sent to WhatsApp is the same for all three values.
        - The follow-up text is best effort: a failed send is only logged, and `message_sent` is still `true` whenever `message` was provided.

        **Use Cases:**
        - Auto-reply "I'm in a meeting, I'll call you back" when declining a call
        - Keep integrations built on the original route working unchanged
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallRejectRequest"
            examples:
              busy_with_message:
                summary: Reject as busy with a follow-up text
                description: Declines the call and sends a short text to the caller right after.
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "5511999999999@s.whatsapp.net"
                  reject_type: busy
                  message: "I'm in a meeting right now, I'll call you back soon."
              declined_silently:
                summary: Reject without a message
                description: Declines the call and sends nothing else; `message_sent` is omitted from the response.
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "123456789012345@lid"
                  reject_type: declined
      responses:
        "200":
          description: Rejection stanza sent. `message_sent` is only present when `message` was provided.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallRejectLegacyResponse"
              examples:
                with_message:
                  summary: Rejected with a follow-up text
                  value:
                    code: 200
                    data:
                      success: true
                      call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                      call_from: "5511999999999@s.whatsapp.net"
                      reject_type: busy
                      action: rejected
                      message_sent: true
                    success: true
                without_message:
                  summary: Rejected without a message
                  value:
                    code: 200
                    data:
                      success: true
                      call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                      call_from: "123456789012345@lid"
                      reject_type: declined
                      action: rejected
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, a required field is missing, `reject_type` is not allowed, or `call_from` is not a parseable JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                missing_fields:
                  summary: call_id or call_from missing
                  value:
                    code: 400
                    error: call_id and call_from are required
                    success: false
                invalid_reject_type:
                  summary: reject_type missing or not allowed
                  value:
                    code: 400
                    error: "invalid reject_type, must be: busy, declined, or unavailable"
                    success: false
                invalid_call_from:
                  summary: call_from is not a valid JID
                  value:
                    code: 400
                    error: "invalid call_from JID: unexpected number of dots in JID"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the session is disconnected, or WhatsApp did not accept the rejection."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Session is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                reject_failed:
                  summary: Rejection stanza failed
                  value:
                    code: 500
                    error: "failed to reject call: websocket not connected"
                    success: false

  /call/reject:
    post:
      tags:
        - Call
      summary: Reject an incoming call
      description: |
        Sends a reject stanza for an inbound call offer through the WhatsApp session, optionally followed by a text
        message to the caller. Works without the native calls engine: only a connected session is needed.

        **Rules:**
        - `call_id` and `call_from` are required; both come from the call offer webhook.
        - `reject_type` defaults to `declined` and must be `busy`, `declined` or `unavailable`. It is echoed back; the stanza itself is the same for all values.
        - The body is validated first (JSON and `reject_type`), then the session (**500** when missing or disconnected), then `call_id`/`call_from`.
        - `message_sent` reports the real outcome of the follow-up text: `false` when no message was requested or when sending it failed. A failed follow-up never turns the response into an error.
        - The response `call_from` is the normalized JID string.

        **Use Cases:**
        - Decline calls outside business hours and point the caller to chat
        - Signal "busy" from a contact-center integration
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallRejectV1Request"
            examples:
              declined_default:
                summary: Reject with the default type
                description: "`reject_type` is omitted, so `declined` is used and no follow-up text is sent."
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "5511999999999@s.whatsapp.net"
              declined_with_followup:
                summary: Reject and send a follow-up text
                description: Rejects as `unavailable` and sends a text to the caller; `message_sent` tells whether the text went out.
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "123456789012345@lid"
                  reject_type: unavailable
                  message: "We can't take calls right now. Please send us a message and we'll reply shortly."
      responses:
        "200":
          description: Reject stanza sent; `message_sent` reports whether the optional follow-up text was delivered to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallRejectV1Response"
              examples:
                rejected_with_followup:
                  summary: Rejected and follow-up sent
                  value:
                    code: 200
                    data:
                      success: true
                      call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                      call_from: "123456789012345@lid"
                      reject_type: unavailable
                      action: rejected
                      message_sent: true
                    success: true
                rejected_only:
                  summary: Rejected without follow-up
                  value:
                    code: 200
                    data:
                      success: true
                      call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                      call_from: "5511999999999@s.whatsapp.net"
                      reject_type: declined
                      action: rejected
                      message_sent: false
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `reject_type` is not allowed, a required field is missing, or `call_from` is not a parseable JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                invalid_reject_type:
                  summary: reject_type not allowed
                  value:
                    code: 400
                    error: invalid reject_type, must be busy|declined|unavailable
                    success: false
                missing_fields:
                  summary: call_id or call_from missing
                  value:
                    code: 400
                    error: call_id and call_from are required
                    success: false
                invalid_call_from:
                  summary: call_from is not a valid JID
                  value:
                    code: 400
                    error: "invalid call_from JID: unexpected number of dots in JID"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the session is disconnected, or WhatsApp did not accept the rejection."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Session is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                reject_failed:
                  summary: Rejection stanza failed
                  value:
                    code: 500
                    error: "failed to reject call: websocket not connected"
                    success: false

  /call/accept:
    post:
      tags:
        - Call
      summary: Send an accept stanza for an incoming call
      description: |
        Sends the signaling `accept` stanza for an inbound call offer through the WhatsApp session. It only answers
        at the protocol level: no audio or video media is set up by this route. To answer a call with real media,
        enable the native calls engine and use `POST /call/answer`.

        **Rules:**
        - `call_id` and `call_from` are required; take both from the call offer webhook.
        - The session must exist and be connected (**500** otherwise); this is checked after the JSON body is decoded and before the required fields.
        - Nothing is stored and no call metrics are produced; the call lifecycle keeps arriving through webhooks.
        - Accepting the same call twice is forwarded to WhatsApp again; the gateway does not de-duplicate.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallAcceptV1Request"
            examples:
              accept:
                summary: Accept a call offer
                description: Uses the call ID and caller JID from the call offer webhook.
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "5511999999999@s.whatsapp.net"
      responses:
        "200":
          description: Accept stanza sent to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallAcceptV1Response"
              example:
                code: 200
                data:
                  success: true
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "5511999999999@s.whatsapp.net"
                  action: accepted
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `call_id`/`call_from` is missing, or `call_from` is not a parseable JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                missing_fields:
                  summary: call_id or call_from missing
                  value:
                    code: 400
                    error: call_id and call_from are required
                    success: false
                invalid_call_from:
                  summary: call_from is not a valid JID
                  value:
                    code: 400
                    error: "invalid call_from JID: unexpected number of dots in JID"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the session is disconnected, or WhatsApp did not accept the stanza."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Session is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                accept_failed:
                  summary: Accept stanza failed
                  value:
                    code: 500
                    error: "failed to accept call: websocket not connected"
                    success: false

  /call/preaccept:
    post:
      tags:
        - Call
      summary: Send a pre-accept (ringing) stanza for an incoming call
      description: |
        Sends the signaling `preaccept` stanza for an inbound call offer, which tells the caller that this device
        is ringing. It does not answer the call and sets up no media.

        **Rules:**
        - `call_id` and `call_from` are required; take both from the call offer webhook.
        - Send it before `POST /call/accept` or `POST /call/reject`; the gateway does not enforce the order.
        - The session must exist and be connected (**500** otherwise).
        - Nothing is stored; the call keeps its lifecycle in WhatsApp and the webhooks.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallAcceptV1Request"
            examples:
              preaccept:
                summary: Signal ringing for a call offer
                description: Uses the call ID and caller LID from the call offer webhook.
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "123456789012345@lid"
      responses:
        "200":
          description: Pre-accept stanza sent to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallAcceptV1Response"
              example:
                code: 200
                data:
                  success: true
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "123456789012345@lid"
                  action: preaccepted
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `call_id`/`call_from` is missing, or `call_from` is not a parseable JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                missing_fields:
                  summary: call_id or call_from missing
                  value:
                    code: 400
                    error: call_id and call_from are required
                    success: false
                invalid_call_from:
                  summary: call_from is not a valid JID
                  value:
                    code: 400
                    error: "invalid call_from JID: unexpected number of dots in JID"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the session is disconnected, or WhatsApp did not accept the stanza."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Session is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                preaccept_failed:
                  summary: Pre-accept stanza failed
                  value:
                    code: 500
                    error: "failed to preaccept call: websocket not connected"
                    success: false

  /call/terminate:
    post:
      tags:
        - Call
      summary: Send a terminate stanza for a call
      description: |
        Sends the signaling `terminate` stanza for a call through the WhatsApp session, ending it at the protocol
        level. Use it for calls handled by signaling only; calls tracked by the native calls engine are ended with
        `POST /call/hangup`, which also tears down their media.

        **Rules:**
        - `call_id` and `call_from` are required; `call_from` is the other party's JID from the call webhook.
        - `reason` is free text forwarded to WhatsApp and defaults to `hangup`.
        - The session must exist and be connected (**500** otherwise).
        - No duration or call record is produced here; the `CallTerminate` webhook event reports the end of the call.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallTerminateRequest"
            examples:
              hangup:
                summary: End a call with the default reason
                description: "`reason` is omitted, so `hangup` is sent."
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "5511999999999@s.whatsapp.net"
              custom_reason:
                summary: End a call with an explicit reason
                description: The reason string is forwarded unchanged and echoed in the response.
                value:
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "123456789012345@lid"
                  reason: timeout
      responses:
        "200":
          description: Terminate stanza sent to WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallTerminateV1Response"
              example:
                code: 200
                data:
                  success: true
                  call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                  call_from: "5511999999999@s.whatsapp.net"
                  reason: hangup
                  action: terminated
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `call_id`/`call_from` is missing, or `call_from` is not a parseable JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                missing_fields:
                  summary: call_id or call_from missing
                  value:
                    code: 400
                    error: call_id and call_from are required
                    success: false
                invalid_call_from:
                  summary: call_from is not a valid JID
                  value:
                    code: 400
                    error: "invalid call_from JID: unexpected number of dots in JID"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the session is disconnected, or WhatsApp did not accept the stanza."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                client_not_initialized:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: client not initialized
                    success: false
                client_not_connected:
                  summary: Session is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                terminate_failed:
                  summary: Terminate stanza failed
                  value:
                    code: 500
                    error: "failed to terminate call: websocket not connected"
                    success: false

  /call/initiate:
    post:
      tags:
        - Call
      summary: Legacy outbound call route (501 unless the dial alias is enabled)
      description: |
        Legacy route. By default it keeps its original contract and answers **501** `make_call_not_implemented` for any body,
        without dialing. Place outbound 1:1 calls with `POST /call/dial`.

        **Rules:**
        - Default: always **501**. The legacy envelope carries `data.error` = `make_call_not_implemented`, `data.reason` pointing to `/call/dial`, and `success: true` (unchanged legacy flag).
        - Opt-in: when the instance sets `GLOBAL_CALL_INITIATE_DIAL_ALIAS=true`, this route becomes an exact alias of `POST /call/dial` (same request, response and errors: `phone` required, `video` optional, **409** without the calls engine, **502** when the call cannot be placed).
        - A group JID is not a valid target; use `/call/group/dial-by-id` for groups.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallDialRequest"
            examples:
              audio_call:
                summary: Voice call to a phone number
                description: The number is resolved to the callee's LID before dialing.
                value:
                  phone: "5511988888888"
              video_call_lid:
                summary: Video call to a LID
                description: A value containing `@` is used as given; `video` offers H.264 video.
                value:
                  phone: "123456789012345@lid"
                  video: true
      responses:
        "501":
          description: "Not Implemented (default): this legacy route does not dial; use `POST /call/dial` or enable `GLOBAL_CALL_INITIATE_DIAL_ALIAS=true`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyErrorEnvelope"
              example:
                code: 501
                data:
                  error: make_call_not_implemented
                  reason: originating a call requires a WebRTC media stack not available in this build
                success: true
        "200":
          description: "Only with `GLOBAL_CALL_INITIATE_DIAL_ALIAS=true`: outbound call placed; use `data.callId` with the other `/call/*` controls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallIdResponse"
              example:
                code: 200
                data:
                  callId: "call_22A9E1B7D3F84C10"
                success: true
        "400":
          description: "Only with `GLOBAL_CALL_INITIATE_DIAL_ALIAS=true`. Bad Request: the body is not valid JSON or `phone` is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: phone is required
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Only with `GLOBAL_CALL_INITIATE_DIAL_ALIAS=true`. Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Only with `GLOBAL_CALL_INITIATE_DIAL_ALIAS=true`. Bad Gateway: the engine could not place the call (peer unreachable, not on WhatsApp, or refused)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: usync returned no LID for 5511988888888 (peer unreachable or not on WhatsApp)
                success: false

  /call/config:
    get:
      tags:
        - Call
      summary: Get the native calls engine configuration
      description: |
        Returns the native calls engine configuration stored for this instance: whether the engine is enabled,
        how inbound calls are handled, auto-recording, and the voice-agent provider settings.

        **Rules:**
        - Reads the saved database row, not the running engine: changes made with `PUT /call/config` only take effect after the instance reconnects.
        - `callProviderToken` is write-only and never returned.
        - Unset provider fields (`callSttUrl`, `callLlmUrl`, `callTtsUrl`, `callSystemPrompt`, `callGreeting`) are returned as empty strings.
        - `callInboundMode` falls back to `webhook` when it was never saved.
        - Works whether or not the engine is enabled; it never answers **409**.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored native calls engine configuration.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallEngineConfigResponse"
              examples:
                webhook_mode:
                  summary: Engine enabled, calls answered and reported by webhook
                  value:
                    code: 200
                    data:
                      callsEnabled: true
                      callInboundMode: webhook
                      callRecord: true
                      callSttUrl: ""
                      callLlmUrl: ""
                      callTtsUrl: ""
                      callSystemPrompt: ""
                      callGreeting: ""
                    success: true
                disabled:
                  summary: Engine disabled (defaults)
                  value:
                    code: 200
                    data:
                      callsEnabled: false
                      callInboundMode: webhook
                      callRecord: false
                      callSttUrl: ""
                      callLlmUrl: ""
                      callTtsUrl: ""
                      callSystemPrompt: ""
                      callGreeting: ""
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration row could not be read from the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "sql: no rows in result set"
                success: false
    put:
      tags:
        - Call
      summary: Update the native calls engine configuration
      description: |
        Saves the native calls engine configuration for this instance. The engine reads it when the WhatsApp
        session connects, so reconnect the instance (`POST /session/disconnect` then `POST /session/connect`)
        for the change to apply.

        **Rules:**
        - This is a full replace, not a merge: an omitted `callsEnabled` or `callRecord` is saved as `false`, and an omitted `callSttUrl`, `callLlmUrl`, `callTtsUrl`, `callSystemPrompt` or `callGreeting` is cleared.
        - `callProviderToken` is the only field kept when omitted; send a new value to replace it.
        - `callInboundMode` defaults to `webhook` when empty and must be `manual`, `bot`, `ivr`, `ai`, `webhook` or `reject` (**400** otherwise).
        - `bot`, `ivr` and `ai` are accepted but the voice-agent is not available in this build: inbound calls in those modes are rejected by the engine.
        - The request body is an object; send `{}` to reset everything except the provider token.

        **Inbound modes:**
        - `webhook`: the engine answers and reports the lifecycle (and the recording when `callRecord` is on) through webhooks.
        - `manual`: the call rings; your application answers with `POST /call/answer` and drives media through the API and streams.
        - `reject`: the engine is not started at all, even with `callsEnabled: true`; every engine endpoint (`/call/dial`, `/call/status`, streams...) then answers **409**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallEngineConfig"
            examples:
              enable_webhook_recording:
                summary: Enable the engine and record every answered call
                description: Inbound calls are answered automatically, recorded to S3 and reported by webhook.
                value:
                  callsEnabled: true
                  callInboundMode: webhook
                  callRecord: true
              enable_manual:
                summary: Enable the engine in manual mode
                description: Calls ring until the application answers them with `POST /call/answer`.
                value:
                  callsEnabled: true
                  callInboundMode: manual
                  callRecord: false
              voice_agent_settings:
                summary: Save voice-agent provider settings
                description: Stores provider endpoints and prompts. The `ai` mode is accepted, but inbound calls are rejected because the voice-agent is not available in this build.
                value:
                  callsEnabled: true
                  callInboundMode: ai
                  callRecord: false
                  callSttUrl: "https://stt.example.com/v1/transcribe"
                  callLlmUrl: "https://llm.example.com/v1/chat"
                  callTtsUrl: "https://tts.example.com/v1/speak"
                  callProviderToken: "sk-example-provider-token"
                  callSystemPrompt: "You are the virtual receptionist of Example Store. Be brief and friendly."
                  callGreeting: "Hello! Thanks for calling Example Store. How can I help you?"
              disable:
                summary: Disable the engine
                description: Saves the engine as disabled; the provider token is kept, every other field is reset.
                value:
                  callsEnabled: false
      responses:
        "200":
          description: Configuration saved. It applies on the next reconnect.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `callInboundMode` is not an accepted mode."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: invalid request body
                    success: false
                invalid_mode:
                  summary: Unknown inbound mode
                  value:
                    code: 400
                    error: "invalid call_inbound_mode: must be manual, bot, ivr, ai, webhook, or reject"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the configuration could not be saved to the database."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "pq: canceling statement due to user request"
                success: false

  /call/status:
    get:
      tags:
        - Call
      summary: List live calls
      description: |
        Returns every call currently tracked by the native calls engine for this instance, inbound and outbound,
        1:1 and group, with its state, direction, media flags and start time.

        **Rules:**
        - Requires the engine to be running for the instance (**409** otherwise, including when the network route is mobile TCP-only).
        - Only live calls are listed; a call disappears as soon as it ends. An empty `calls` array means no call is in progress.
        - `state` is one of `idle`, `calling`, `ringing`, `connecting`, `active`, `ended` or `unknown`.
        - The order of the array is not stable.

        **Use Cases:**
        - Find the `callId` of an inbound call to answer it in manual mode
        - Show a live-calls panel in a dashboard
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Snapshot of the live calls.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallStatusResponse"
              examples:
                two_calls:
                  summary: One active and one ringing call
                  value:
                    code: 200
                    data:
                      calls:
                        - callId: "call_17B3F2A1C9D04E8F"
                          peer: "123456789012345@lid"
                          state: active
                          direction: inbound
                          video: false
                          recording: true
                          startedAt: "2026-09-15T14:21:08.412Z"
                        - callId: "call_22A9E1B7D3F84C10"
                          peer: "123456789012346@lid"
                          state: ringing
                          direction: outbound
                          video: true
                          recording: false
                          startedAt: "2026-09-15T14:22:55.090Z"
                    success: true
                no_calls:
                  summary: No call in progress
                  value:
                    code: 200
                    data:
                      calls: []
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/dial:
    post:
      tags:
        - Call
      summary: Place an outbound 1:1 call
      description: |
        Places an outbound voice or video call to one person with the native calls engine (WebRTC with Opus audio
        and H.264 video) and returns the engine `callId` used by every other control endpoint and stream.

        **Target formats:**
        - Digits with country code and no `+` (for example `5511988888888`): resolved to the callee's LID before dialing.
        - Phone JID (`5511988888888@s.whatsapp.net`) or LID (`123456789012345@lid`): anything containing `@` is used as given.

        **Rules:**
        - `phone` is required; `video: true` offers H.264 video, otherwise the call is voice only.
        - A plain number is resolved first because the typed number is not always the canonical one (many Brazilian mobiles are stored without the ninth digit). If the lookup fails, dialing is still attempted with the input.
        - A group JID (`@g.us`) is not a valid target; use `/call/group/dial-by-id` or `/call/group/dial`.
        - Works in every inbound mode except `reject` (which keeps the engine off) once the engine is running; **409** otherwise.
        - The call's lifecycle is reported by the `call.offer`, `call.accept` and `call.terminate` webhooks; with `callRecord` on, the call is recorded once connected.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallDialRequest"
            examples:
              voice_call:
                summary: Voice call to a phone number
                description: The number is resolved to the callee's LID before the offer is sent.
                value:
                  phone: "5511988888888"
              video_call:
                summary: Video call to a phone number
                description: Same resolution as a voice call, with H.264 video offered.
                value:
                  phone: "5511988888888"
                  video: true
              voice_call_lid:
                summary: Voice call to a LID
                description: A value containing `@` skips the number lookup and is used exactly as given.
                value:
                  phone: "123456789012345@lid"
      responses:
        "200":
          description: Outbound call placed; `data.callId` identifies it in every other call endpoint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallIdResponse"
              example:
                code: 200
                data:
                  callId: "call_22A9E1B7D3F84C10"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `phone` is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: phone is required
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine could not place the call. Every dial failure maps here, including an unknown number."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_lid:
                  summary: Number not on WhatsApp
                  value:
                    code: 502
                    error: usync returned no LID for 5511988888888 (peer unreachable or not on WhatsApp)
                    success: false
                no_devices:
                  summary: Peer has no reachable device
                  value:
                    code: 502
                    error: peer 123456789012345@lid has no devices (unreachable / not on WhatsApp)
                    success: false

  /call/answer:
    post:
      tags:
        - Call
      summary: Answer a ringing inbound call
      description: |
        Answers an inbound call tracked by the native calls engine and starts its media session. Meant for
        `callInboundMode: manual`, where calls ring until the application decides; in `webhook` mode the engine
        answers by itself.

        **Rules:**
        - `callId` is the engine call ID from `GET /call/status` or the `call.offer` webhook, not the WhatsApp signaling `call_id`.
        - **404** when no live call has that ID (already ended, or it belongs to another instance).
        - Engine refusals (for example the call is no longer ringing) are returned as **400** with the engine message.
        - After answering, attach media with `/call/{call_id}/stream`, `/call/play` or `/call/record/start`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallIdRequest"
            examples:
              answer:
                summary: Answer a ringing call
                description: Answers the inbound call listed by `GET /call/status`.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Call answered.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: `callId` is missing, or the engine refused to answer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                engine_refused:
                  summary: Engine refused the answer
                  value:
                    code: 400
                    error: "meowcaller: call is not active"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/hangup:
    post:
      tags:
        - Call
      summary: Hang up a live call
      description: |
        Ends a call tracked by the native calls engine, in either direction, and tears down its media session.
        An active recording is finalized and uploaded as part of the call end.

        **Rules:**
        - `callId` is the engine call ID returned by `/call/dial`, `/call/group/dial*`, `/call/link/join` or listed by `GET /call/status`.
        - **404** when the call is not live anymore; hanging up twice returns **404** the second time.
        - Engine errors while ending the call are returned as **400**.
        - The `call.terminate` webhook (and `call.recording_ready`, when recording) follows asynchronously.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallIdRequest"
            examples:
              hangup:
                summary: End a call
                description: Ends the call placed earlier with `/call/dial`.
                value:
                  callId: "call_22A9E1B7D3F84C10"
      responses:
        "200":
          description: Hangup sent and media torn down.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: `callId` is missing, or the engine failed to end the call."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: callId is required
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/play:
    post:
      tags:
        - Call
      summary: Play audio into a live call
      description: |
        Plays an audio clip to the other side of a live call. The clip is fetched or decoded up front, then queued
        on the call's player; the response returns as soon as playback is scheduled, not when it finishes.

        **Features:**
        - **Formats**: WAV, Ogg/Opus and MP3, detected from the file content (anything that is not WAV or Ogg is decoded as MP3).
        - **Resampling**: every clip is converted to the call's native 16 kHz mono.
        - **Sources**: a public URL (downloaded with a 30 s timeout, at most 64 MiB) or inline base64.

        **Rules:**
        - `callId` is required, plus one of `audioUrl` or `audioBase64`; when both are sent, `audioUrl` wins.
        - `audioBase64` is standard base64 without a `data:` prefix.
        - Download, decode and format errors are returned as **400** with the underlying message, before the call is looked up.
        - **404** when the call already ended or the ID belongs to another instance.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallPlayRequest"
            examples:
              play_url:
                summary: Play an MP3 from a URL
                description: The file is downloaded by the gateway, decoded and resampled before playback starts.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
                  audioUrl: "https://cdn.example.com/audio/welcome-message.mp3"
              play_base64:
                summary: Play a short WAV sent inline
                description: Base64 of a WAV file (truncated here); the container is detected from the RIFF header.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
                  audioBase64: "UklGRiQAAABXQVZFZm10IBAAAAABAAEAgD4AAAB9AAACABAAZGF0YQAAAAA="
      responses:
        "200":
          description: Playback scheduled on the call.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: `callId` or the audio source is missing, the base64 is invalid, or the audio could not be downloaded or decoded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                missing_source:
                  summary: Neither audioUrl nor audioBase64
                  value:
                    code: 400
                    error: audioUrl or audioBase64 is required
                    success: false
                invalid_base64:
                  summary: audioBase64 is not valid base64
                  value:
                    code: 400
                    error: invalid audioBase64
                    success: false
                url_status:
                  summary: audioUrl answered with an error status
                  value:
                    code: 400
                    error: "voip: audio url status 404"
                    success: false
                too_short:
                  summary: Audio payload shorter than 4 bytes
                  value:
                    code: 400
                    error: "voip: audio payload too short"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/record/start:
    post:
      tags:
        - Call
      summary: Start recording a live call
      description: |
        Starts recording the other party's audio of a live call to a WAV file in the durable recording spool.
        Stop it with `POST /call/record/stop`, or let the call end: the recording is then finalized, uploaded
        to S3 and announced by the `call.recording_ready` webhook.

        **Rules:**
        - Requires object storage (S3) for the instance and an available recording spool; otherwise **400** with the reason.
        - Only the peer's inbound audio is recorded, not the audio played into the call.
        - Recording and the `/call/{call_id}/stream` audio WebSocket share the call's single inbound audio sink, so they cannot run at the same time on one call.
        - Starting while a recording is already running is a no-op and still returns `ok: true`.
        - `callRecord: true` in `PUT /call/config` starts recording automatically for every connected call.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallIdRequest"
            examples:
              start_recording:
                summary: Record a live call
                description: Starts recording the peer audio of an active call.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Recording started (or was already running).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: `callId` is missing, S3 is not configured, or the recording spool is unavailable or full."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                no_storage:
                  summary: S3 not configured
                  value:
                    code: 400
                    error: "voip: recording requires object storage (S3) to be configured"
                    success: false
                spool_unavailable:
                  summary: Recording spool unavailable
                  value:
                    code: 400
                    error: "voip: durable recording spool is unavailable"
                    success: false
                spool_quota:
                  summary: Recording spool quota exceeded
                  value:
                    code: 400
                    error: "voip: durable recording spool quota exceeded"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance (or it ended while the recorder was starting)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/record/stop:
    post:
      tags:
        - Call
      summary: Stop recording and upload the file
      description: |
        Stops the active recording of a live call, uploads the WAV to object storage and returns its media URL.
        The call itself keeps running. The `call.recording_ready` webhook is emitted for the uploaded file.

        **Rules:**
        - `mediaKey` holds the **URL** of the uploaded recording (the field name is historical).
        - When no recording is active, the response is still **200** with `mediaKey` as an empty string.
        - Upload or spool failures are returned as **400** with the reason; the spooled file is retried in the background.
        - Calling stop while the same recording is already being finalized (for example by the call ending) returns **400**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallIdRequest"
            examples:
              stop_recording:
                summary: Stop recording and upload
                description: Finalizes the WAV of the running recording and uploads it to S3.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Recording finalized; `data.mediaKey` is the uploaded file URL, or empty when nothing was recording.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallRecordingStopResponse"
              examples:
                uploaded:
                  summary: Recording uploaded
                  value:
                    code: 200
                    data:
                      ok: true
                      mediaKey: "https://my-bucket.s3.amazonaws.com/call-recordings/9b2f6a1e-4c1d-4e0b-8f7a-2d3c4b5a6e7f.wav"
                    success: true
                not_recording:
                  summary: No active recording
                  value:
                    code: 200
                    data:
                      ok: true
                      mediaKey: ""
                    success: true
        "400":
          description: "Bad Request: `callId` is missing, or the recording could not be finalized or uploaded."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                already_finalizing:
                  summary: Recording already being finalized
                  value:
                    code: 400
                    error: recording is already being finalized
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/group/dial:
    post:
      tags:
        - Call
      summary: Place a group call to a list of people
      description: |
        Places an outbound group call to an explicit list of people with the native calls engine and returns its
        `callId`. Optionally binds the call to an existing WhatsApp group while ringing only some of its members.

        **Features:**
        - **Ad-hoc group call**: ring 2 to 31 people who do not need to share a group.
        - **Group-bound subset**: with `groupJid`, the call belongs to that group but only `targets` are rung. This is the only way to call a group larger than WhatsApp's limit of 32 connected participants, which `/call/group/dial-by-id` refuses.
        - **Video**: `video: true` offers H.264 video to every participant.

        **Rules:**
        - `targets` needs at least two entries (**400** otherwise); each is a phone number, phone JID or LID.
        - The engine discards duplicates and this account itself; more than 31 remote targets, or fewer than two distinct ones after that, fails with **502** and the engine message.
        - Any failure while resolving targets or sending the offer is **502**; a refusal by WhatsApp includes its error code (`whatsapp refused the call request (error N)`).
        - The call is listed by `GET /call/status` and emits the same call webhooks as a 1:1 call.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallGroupDialRequest"
            examples:
              voice_group:
                summary: Voice group call to three people
                description: An ad-hoc call; the targets do not need to share a WhatsApp group.
                value:
                  targets:
                    - "5511988888888"
                    - "5511977777777"
                    - "123456789012345@lid"
              video_group:
                summary: Video group call
                description: Same as a voice group call, with H.264 video offered.
                value:
                  targets:
                    - "5511988888888@s.whatsapp.net"
                    - "5511977777777@s.whatsapp.net"
                  video: true
              bound_subset:
                summary: Call part of a large group
                description: The call belongs to the group, but only the listed members are rung.
                value:
                  groupJid: "120363000000000011@g.us"
                  targets:
                    - "5511988888888"
                    - "5511977777777"
      responses:
        "200":
          description: Group call placed; `data.callId` identifies it in the other call endpoints.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallIdResponse"
              example:
                code: 200
                data:
                  callId: "call_5C1E9A7B3D2F4E60"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `targets` has fewer than two entries."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: targets is required and needs at least two entries
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine could not place the group call, or WhatsApp refused it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                too_many_targets:
                  summary: More than 31 remote targets
                  value:
                    code: 502
                    error: "meowcaller: group call supports at most 31 remote targets"
                    success: false
                unreachable_participant:
                  summary: A target has no reachable device
                  value:
                    code: 502
                    error: "meowcaller: participant 123456789012345@lid has no reachable devices"
                    success: false
                refused:
                  summary: WhatsApp refused the call
                  value:
                    code: 502
                    error: whatsapp refused the call request (error 431)
                    success: false

  /call/group/dial-by-id:
    post:
      tags:
        - Call
      summary: Place a group call to every member of a WhatsApp group
      description: |
        Places an outbound group call bound to an existing WhatsApp group: the engine reads the group roster and
        rings every other member. No target list is needed.

        **Rules:**
        - `groupJid` is required and must be a group this account belongs to (`...@g.us`).
        - The group needs 2 to 31 other members. Larger groups are refused; call a subset with `POST /call/group/dial` and `groupJid` instead.
        - Resolution, size and signaling failures are **502** with the engine or WhatsApp message.
        - `video: true` offers H.264 video.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallGroupDialByIDRequest"
            examples:
              voice_group:
                summary: Voice call to a whole group
                description: Every member of the group except this account is rung.
                value:
                  groupJid: "120363000000000011@g.us"
              video_group:
                summary: Video call to a whole group
                description: Same as the voice call, with H.264 video offered.
                value:
                  groupJid: "120363000000000011@g.us"
                  video: true
      responses:
        "200":
          description: Group call placed; `data.callId` identifies it in the other call endpoints.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallIdResponse"
              example:
                code: 200
                data:
                  callId: "call_5C1E9A7B3D2F4E60"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `groupJid` is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: groupJid is required
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the group roster could not be used for a call, or WhatsApp refused it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                too_many_members:
                  summary: Group larger than the call limit
                  value:
                    code: 502
                    error: "meowcaller: group call by ID has 45 remote members; at most 31 can be called"
                    success: false
                too_few_members:
                  summary: Group with fewer than two other members
                  value:
                    code: 502
                    error: "meowcaller: group call by ID requires at least two remote members"
                    success: false
                refused:
                  summary: WhatsApp refused the call
                  value:
                    code: 502
                    error: whatsapp refused the call request (error 431)
                    success: false

  /call/participants/add:
    post:
      tags:
        - Call
      summary: Invite people into a live call
      description: |
        Invites one or more people into a call that is already established. Each target is invited independently,
        so one failure does not stop the others and the response lists exactly which targets failed.

        **Rules:**
        - `callId` and a non-empty `targets` array are required; each target is a phone number, phone JID or LID.
        - The HTTP status is **200** even when some or all invites fail: check `data.ok` and `data.failed` (entries are `"<target>: <reason>"`).
        - Inviting someone already in the roster, or this account itself, is reported in `failed`.
        - An unknown or already-ended `callId` answers **404**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallParticipantsAddRequest"
            examples:
              add_one:
                summary: Invite one person
                description: Adds one phone number to an established call.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  targets:
                    - "5511966666666"
              add_several:
                summary: Invite several people
                description: Every target is invited on its own; failures are reported per target.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  targets:
                    - "5511966666666"
                    - "123456789012347@lid"
      responses:
        "200":
          description: Invites processed. `data.ok` is false when at least one target failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallParticipantsAddResponse"
              examples:
                all_invited:
                  summary: Every target invited
                  value:
                    code: 200
                    data:
                      ok: true
                      failed: []
                    success: true
                partial_failure:
                  summary: One target failed
                  value:
                    code: 200
                    data:
                      ok: false
                      failed:
                        - "123456789012347@lid: meowcaller: participant is already in the call roster"
                    success: true
        "400":
          description: "Bad Request: the body is not valid JSON, or `callId`/`targets` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: callId and targets are required
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/participants/ring:
    post:
      tags:
        - Call
      summary: Ring a participant again
      description: |
        Rings again a participant who is already on the roster of a live group call but is not connected, for
        example someone who missed or declined the first ring.

        **Rules:**
        - `callId` and `target` are required strings; `target` is a phone number, phone JID or LID.
        - The target must be in the call roster and not connected; otherwise the engine refuses with **502** (`participant is not in the call roster`, `participant is already connected`).
        - To bring in someone new, use `POST /call/participants/add`.
        - **404** when the group call is no longer tracked.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallParticipantRingRequest"
            examples:
              ring_again:
                summary: Ring a member who did not answer
                description: The target is on the roster of the group call but not connected.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  target: "5511977777777"
      responses:
        "200":
          description: Ring sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object of strings, or `callId`/`target` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object of strings
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_fields:
                  summary: callId or target missing
                  value:
                    code: 400
                    error: callId and target are required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine or WhatsApp refused to ring the participant."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_in_roster:
                  summary: Target is not on the roster
                  value:
                    code: 502
                    error: "meowcaller: participant is not in the call roster"
                    success: false
                already_connected:
                  summary: Target is already connected
                  value:
                    code: 502
                    error: "meowcaller: participant is already connected"
                    success: false

  /call/link/create:
    post:
      tags:
        - Call
      summary: Create a WhatsApp call link
      description: |
        Creates a reusable public call link (`https://call.whatsapp.com/audio/...` or `/video/...`). Anyone with the
        link can join the call, subject to the waiting room when approval is required.

        **Rules:**
        - The body is optional: an empty or missing body creates a voice link; `video: true` creates a video link.
        - Creating a link does not start a call; join it with `POST /call/link/join` to be in the call.
        - Signaling failures are **502** with the engine message.
        - Requires the native calls engine (**409** otherwise).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallLinkCreateRequest"
            examples:
              voice_link:
                summary: Create a voice call link
                description: Equivalent to sending no body.
                value:
                  video: false
              video_link:
                summary: Create a video call link
                description: The returned URL uses the `/video/` path.
                value:
                  video: true
      responses:
        "200":
          description: Call link created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallLinkCreateResponse"
              example:
                code: 200
                data:
                  token: "Xk3pQ9aB7cD2eF4g"
                  url: "https://call.whatsapp.com/video/Xk3pQ9aB7cD2eF4g"
                  video: true
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp did not create the link."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                signaling_unavailable:
                  summary: Call-link signaling unavailable
                  value:
                    code: 502
                    error: "meowcaller: call-link signaling is unavailable"
                    success: false
                no_token:
                  summary: WhatsApp answered without a token
                  value:
                    code: 502
                    error: "meowcaller: create call link returned no token"
                    success: false

  /call/link/preview:
    get:
      tags:
        - Call
      summary: Preview a call link without joining
      description: |
        Reads the metadata of a WhatsApp call link (media type, whether approval is required, whether this account
        is an admin, and the creator) without joining the call.

        **Rules:**
        - The query parameter `token` is the **call-link** token or full URL, not the API token. Authenticate with the `token` **header** here: a query `token` is read as the call-link value and a request without the header answers **401**.
        - A full URL must be `https://call.whatsapp.com/audio/<token>` or `/video/<token>`; anything else fails with **502** `invalid WhatsApp call-link URL`.
        - `video=true` (exactly `true`) previews it as a video link; any other value means voice.
        - `creator` and `creatorPhone` are omitted when WhatsApp does not disclose them.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: token
          in: query
          required: true
          description: Call-link token or full `https://call.whatsapp.com/...` URL. Not the API authentication token.
          schema:
            type: string
          example: "https://call.whatsapp.com/video/Xk3pQ9aB7cD2eF4g"
        - name: video
          in: query
          required: false
          description: "`true` previews the link as a video link; any other value is voice."
          schema:
            type: boolean
            default: false
          example: true
      responses:
        "200":
          description: Call-link metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallLinkPreviewResponse"
              example:
                code: 200
                data:
                  token: "Xk3pQ9aB7cD2eF4g"
                  video: true
                  approvalRequired: true
                  isAdmin: false
                  creator: "123456789012345@lid"
                  creatorPhone: "5511988888888@s.whatsapp.net"
                success: true
        "400":
          description: "Bad Request: the `token` query parameter is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: token is required
                success: false
        "401":
          description: Missing or invalid user token. On this route the API token must be sent in the `token` header.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the link is malformed or WhatsApp could not resolve it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_url:
                  summary: Not a WhatsApp call-link URL
                  value:
                    code: 502
                    error: "meowcaller: invalid WhatsApp call-link URL"
                    success: false
                no_result:
                  summary: WhatsApp returned no preview
                  value:
                    code: 502
                    error: "meowcaller: preview call link returned no result"
                    success: false

  /call/link/join:
    post:
      tags:
        - Call
      summary: Join a call link
      description: |
        Joins the call behind a WhatsApp call link, or enters its waiting room when the link requires approval. The
        resulting call is tracked like any other, so `/call/status`, the streams and `/call/hangup` work with the
        returned `callId`.

        **Rules:**
        - `token` is required and accepts the bare call-link token or the full `https://call.whatsapp.com/audio|video/<token>` URL.
        - When approval is required, the call stays in the waiting room until an admin admits this account; follow it with `GET /call/{call_id}/waiting-room` or the `/call/{call_id}/events` stream.
        - Malformed links and WhatsApp refusals are **502**.
        - `video: true` joins with video.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallLinkJoinRequest"
            examples:
              join_with_token:
                summary: Join with the bare token
                description: Joins as a voice participant.
                value:
                  token: "Xk3pQ9aB7cD2eF4g"
              join_with_url:
                summary: Join a video link by URL
                description: The token is extracted from the URL; `video` joins with the camera track.
                value:
                  token: "https://call.whatsapp.com/video/Xk3pQ9aB7cD2eF4g"
                  video: true
      responses:
        "200":
          description: Joined (or waiting for approval); `data.callId` identifies the call.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallIdResponse"
              example:
                code: 200
                data:
                  callId: "call_8D4B2E6A1C9F3E70"
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `token` is empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: token is required
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the link is malformed, or WhatsApp refused the join."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Token contains a slash but is not a URL
                  value:
                    code: 502
                    error: "meowcaller: invalid call-link token"
                    success: false
                refused:
                  summary: WhatsApp refused the join
                  value:
                    code: 502
                    error: whatsapp refused the call request (error 431)
                    success: false

  /call/waiting-room/admit:
    post:
      tags:
        - Call
      summary: Admit a user from the call-link waiting room
      description: |
        Lets one user who is waiting for approval into a call-link call. Only an admin of the call link can admit.

        **Rules:**
        - `callId` and `user` are required strings; `user` is the waiting user's JID as listed by `GET /call/{call_id}/waiting-room`.
        - Refusals (not an admin, user no longer waiting, no waiting room) come back as **502** with the WhatsApp or engine message.
        - A body whose values are not strings is rejected with **400** `invalid payload`.
        - **404** when the call-link call has already ended.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallWaitingRoomUserRequest"
            examples:
              admit_user:
                summary: Admit a waiting user
                description: Uses a JID taken from the waiting-room roster.
                value:
                  callId: "call_8D4B2E6A1C9F3E70"
                  user: "123456789012345@lid"
      responses:
        "200":
          description: User admitted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object of strings, or `callId`/`user` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object of strings
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_fields:
                  summary: callId or user missing
                  value:
                    code: 400
                    error: callId and user are required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp or the engine refused to admit the user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: whatsapp refused the call request (error 431)
                success: false

  /call/waiting-room/deny:
    post:
      tags:
        - Call
      summary: Deny a user in the call-link waiting room
      description: |
        Rejects one user who is waiting for approval to join a call-link call; the user does not enter the call.
        Only an admin of the call link can deny.

        **Rules:**
        - `callId` and `user` are required strings; take `user` from `GET /call/{call_id}/waiting-room` or the `waiting-room` event.
        - WhatsApp or engine refusals are **502**; a live call that is no longer tracked is **404**.
        - Denying does not block the user from asking to join again.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallWaitingRoomUserRequest"
            examples:
              deny_user:
                summary: Deny a waiting user
                description: Uses a JID taken from the waiting-room roster.
                value:
                  callId: "call_8D4B2E6A1C9F3E70"
                  user: "123456789012346@lid"
      responses:
        "200":
          description: User denied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object of strings, or `callId`/`user` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object of strings
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_fields:
                  summary: callId or user missing
                  value:
                    code: 400
                    error: callId and user are required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp or the engine refused to deny the user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: whatsapp refused the call request (error 431)
                success: false

  /call/waiting-room/approval:
    post:
      tags:
        - Call
      summary: Turn call-link approval on or off
      description: |
        Turns the waiting room of a call-link call on or off. With approval on, new joiners wait until an admin
        admits them; with approval off, they join directly. Only an admin of the call link can change it.

        **Rules:**
        - `callId` is required. `enabled` defaults to `true` when omitted and must be a JSON boolean (**400** for `"true"` as a string).
        - The response echoes the applied value in `data.enabled`.
        - Refusals by WhatsApp (for example not an admin) are **502**; an untracked call is **404**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallApprovalRequest"
            examples:
              require_approval:
                summary: Require approval to join
                description: New joiners go to the waiting room.
                value:
                  callId: "call_8D4B2E6A1C9F3E70"
                  enabled: true
              open_link:
                summary: Let anyone with the link join directly
                description: Disables the waiting room.
                value:
                  callId: "call_8D4B2E6A1C9F3E70"
                  enabled: false
              default_true:
                summary: Omit enabled
                description: Without `enabled` the handler applies `true`.
                value:
                  callId: "call_8D4B2E6A1C9F3E70"
      responses:
        "200":
          description: Approval setting applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallEnabledToggleResponse"
              example:
                code: 200
                data:
                  ok: true
                  enabled: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `callId` is missing, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                not_boolean:
                  summary: enabled is not a boolean
                  value:
                    code: 400
                    error: enabled must be a boolean
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp refused the change."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: whatsapp refused the call request (error 431)
                success: false

  /call/reaction:
    post:
      tags:
        - Call
      summary: Send an emoji reaction inside a live call
      description: |
        Shows an emoji reaction to everyone in a live call, like the reaction bar of the official apps. Reactions
        are transient: other participants see them briefly and they are not stored.

        **Rules:**
        - `callId` and `emoji` are required strings; an empty `emoji` is rejected, so a reaction cannot be cleared through this route.
        - The emoji must be valid UTF-8; engine errors (for example the call has no media yet) are **502**.
        - Reactions from other participants arrive as `reaction` events on `GET /call/{call_id}/events`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallReactionRequest"
            examples:
              thumbs_up:
                summary: React with a thumbs up
                description: Sends a thumbs-up to every participant.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  emoji: "👍"
              heart:
                summary: React with a heart
                description: Any single emoji works the same way.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  emoji: "❤️"
      responses:
        "200":
          description: Reaction sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallOkResponse"
              example:
                code: 200
                data:
                  ok: true
                success: true
        "400":
          description: "Bad Request: the body is not a JSON object of strings, or `callId`/`emoji` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object of strings
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_fields:
                  summary: callId or emoji missing
                  value:
                    code: 400
                    error: callId and emoji are required
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine could not send the reaction."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "meowcaller: call is not active"
                success: false

  /call/hand:
    post:
      tags:
        - Call
      summary: Raise or lower your hand in a live call
      description: |
        Raises or lowers this account's hand in a live call. The state is persistent: it stays visible to the
        other participants until it is lowered again.

        **Rules:**
        - `callId` is required. `raised` defaults to `true` when omitted and must be a JSON boolean.
        - The applied value is echoed in `data.raised`.
        - Other participants' hand changes arrive as `hand` events on `GET /call/{call_id}/events`.
        - Engine errors are **502**; an untracked call is **404**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallHandRequest"
            examples:
              raise_hand:
                summary: Raise your hand
                description: Shows the raised-hand indicator to the other participants.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  raised: true
              lower_hand:
                summary: Lower your hand
                description: Removes the raised-hand indicator.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  raised: false
              default_true:
                summary: Omit raised
                description: Without `raised` the handler raises the hand.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
      responses:
        "200":
          description: Hand state applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallHandResponse"
              example:
                code: 200
                data:
                  ok: true
                  raised: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `callId` is missing, or `raised` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                not_boolean:
                  summary: raised is not a boolean
                  value:
                    code: 400
                    error: raised must be a boolean
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine could not change the hand state."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "meowcaller: call is not active"
                success: false

  /call/video:
    post:
      tags:
        - Call
      summary: Start or stop sending your video in a live call
      description: |
        Starts or stops this account's outbound video track in a live call, for example to upgrade a voice call to
        video. The picture itself is sent as H.264 access units through `GET /call/{call_id}/video/stream`.

        **Rules:**
        - `callId` is required. `enabled` defaults to `true` and must be a JSON boolean.
        - `enabled: true` requests a voice-to-video upgrade; outbound video only flows after the other side accepts it.
        - `enabled: false` stops the outbound video while the peer's video and the audio keep flowing.
        - Engine refusals (for example the call is not active) are **502**; an untracked call is **404**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallVideoToggleRequest"
            examples:
              start_video:
                summary: Turn your camera on
                description: Requests the upgrade to video for this call.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
                  enabled: true
              stop_video:
                summary: Turn your camera off
                description: Stops the outbound video track only.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
                  enabled: false
              default_true:
                summary: Omit enabled
                description: Without `enabled` the handler turns video on.
                value:
                  callId: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Video state change requested.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallEnabledToggleResponse"
              example:
                code: 200
                data:
                  ok: true
                  enabled: true
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `callId` is missing, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                not_boolean:
                  summary: enabled is not a boolean
                  value:
                    code: 400
                    error: enabled must be a boolean
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine could not change the video state."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "meowcaller: call is not active"
                success: false

  /call/screenshare:
    post:
      tags:
        - Call
      summary: Start or stop screen sharing in a live call
      description: |
        Announces to the other participants that this account started or stopped sharing its screen. The screen
        share state is independent from the camera state set with `POST /call/video`.

        **Rules:**
        - `callId` is required. `enabled` defaults to `true` when omitted and must be a JSON boolean.
        - The frames themselves are pushed through `GET /call/{call_id}/video/stream`; this route only changes the announced state.
        - Other participants' screen-share changes arrive as `screenshare` events on `GET /call/{call_id}/events`.
        - Engine errors are **502**; an untracked call is **404**.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallScreenShareRequest"
            examples:
              start_screenshare:
                summary: Start sharing the screen
                description: Announces a screen share to the other participants.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  enabled: true
              stop_screenshare:
                summary: Stop sharing the screen
                description: Announces that the screen share ended.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
                  enabled: false
              default_true:
                summary: Omit enabled
                description: Without `enabled` the handler starts the screen share.
                value:
                  callId: "call_5C1E9A7B3D2F4E60"
      responses:
        "200":
          description: Screen-share state applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallEnabledToggleResponse"
              example:
                code: 200
                data:
                  ok: true
                  enabled: false
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `callId` is missing, or `enabled` is not a boolean."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payload:
                  summary: Body is not a JSON object
                  value:
                    code: 400
                    error: invalid payload
                    success: false
                missing_call_id:
                  summary: callId missing
                  value:
                    code: 400
                    error: callId is required
                    success: false
                not_boolean:
                  summary: enabled is not a boolean
                  value:
                    code: 400
                    error: enabled must be a boolean
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this `callId` for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "502":
          description: "Bad Gateway: the engine could not change the screen-share state."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: "meowcaller: call is not active"
                success: false

  /call/{call_id}/stream:
    get:
      tags:
        - Call
      summary: Bidirectional raw PCM audio WebSocket for a live call
      description: |
        Upgrades to a WebSocket that bridges raw PCM audio with a live call: the other party's audio is pushed to
        the client, and audio the client sends is played to the other party. This is how a custom media
        application (speech bot, recorder, softphone) connects to a call answered with the native calls engine.

        **Wire format:**
        - Server to client: binary messages of signed 16-bit little-endian PCM, 16 kHz, mono; one message per 60 ms frame (960 samples, 1920 bytes).
        - Client to server: binary messages with the same PCM format, any length up to 64 KiB; they are split into 60 ms frames and played in order (the last partial frame is zero-padded). Text messages are ignored.
        - Frames that arrive faster than they can be played are dropped once 128 frames are queued.

        **Rules:**
        - Authenticate with the header `token` or, from a browser `WebSocket`, the query string `?token=`.
        - The engine and the call are checked **before** the upgrade, so **409** and **404** arrive as JSON envelopes; a request that is not a WebSocket upgrade gets a plain-text **400**.
        - The server pings every 20 s; a client that neither answers pings nor sends data for 60 s is disconnected. A write blocked for 5 s also closes the socket.
        - The socket and call recording share the call's single inbound audio sink, so they cannot run at the same time on one call.
        - Closing the socket stops the playback fed by it; the call itself keeps running.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of the live call (from `/call/dial` or `GET /call/status`).
          schema:
            type: string
          example: "call_17B3F2A1C9D04E8F"
      responses:
        "101":
          description: "Switching Protocols: the WebSocket is open and PCM frames start flowing."
        "400":
          description: "Bad Request: the request is not a valid WebSocket upgrade. Written by the WebSocket library as plain text."
          content:
            text/plain:
              schema:
                type: string
                description: HTTP status text.
                example: Bad Request
              example: Bad Request
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this ID for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/{call_id}/video/stream:
    get:
      tags:
        - Call
      summary: Bidirectional H.264 video WebSocket for a live call
      description: |
        Upgrades to a WebSocket that carries encoded H.264 video for a live call. The gateway never encodes or
        decodes pixels: the client consumes the other party's access units and produces its own.

        **Wire format:**
        - Client to server (binary): one H.264 Annex-B access unit per message, at most 1 MiB. The first access unit WhatsApp accepts announces the local camera as on; closing the socket announces it as off.
        - Server to client, default framing (binary): bare Annex-B access units from every camera muxed into one stream. Only decodable while a single camera is on.
        - Server to client with `fmt=participant` (binary): `[1 byte version = 2][1 byte id length][participant id, UTF-8][Annex-B access unit]`, one decoder per participant. Required for group video.
        - Text messages carry JSON control events. Server to client: `participant` (new camera with `id`, `sender`, `device`, `ssrc`, optional `orientation`), `orientation` (rotation change), `video-ready` (outbound video media is live), `keyframe` (the peer asked for an IDR). Client to server: `{"type":"hello","fmt":"participant"}` switches to participant framing; `{"type":"client-log","message":"..."}` writes a diagnostic to the server log.

        **Rules:**
        - Authenticate with the header `token` or, from a browser `WebSocket`, the query string `?token=`.
        - **409** and **404** are returned as JSON before the upgrade; a request that is not a WebSocket upgrade gets a plain-text **400**.
        - Under backpressure the server sheds queued frames forward to the next keyframe instead of adding latency (8 frames per camera, 24 MiB per socket, 32 cameras).
        - Outbound frames sent before the call has video media are dropped silently; `GET /call/{call_id}/video/stats` shows the counters.
        - One socket per call: a reconnect replaces the previous socket, which stops receiving frames. Pings every 20 s, 60 s idle timeout, 20 s write timeout.
        - Use `POST /call/video` to start or stop the outbound video track itself.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of the live call.
          schema:
            type: string
          example: "call_17B3F2A1C9D04E8F"
        - name: fmt
          in: query
          required: false
          description: Inbound framing. `participant` tags every access unit with its participant (needed for group calls); any other value or absence keeps bare access units.
          schema:
            type: string
            enum:
              - participant
          example: participant
      responses:
        "101":
          description: "Switching Protocols: the WebSocket is open; binary access units and JSON control events start flowing."
        "400":
          description: "Bad Request: the request is not a valid WebSocket upgrade. Written by the WebSocket library as plain text."
          content:
            text/plain:
              schema:
                type: string
                description: HTTP status text.
                example: Bad Request
              example: Bad Request
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this ID for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/{call_id}/video/state:
    get:
      tags:
        - Call
      summary: Stream the peer's video state (Server-Sent Events)
      description: |
        Opens a Server-Sent Events stream with the other party's video state for a live call: camera on or off,
        a mid-call upgrade from voice to video, and device orientation for rendering the picture upright.

        **Event format:**
        - The stream starts with `retry: 3000` so `EventSource` reconnects after 3 s.
        - Each change is one `data:` line with a JSON object: `active` (camera on), `upgrade` (voice-to-video upgrade request), `raw` (unmapped WhatsApp state value) and `orientation` (0-3, rotate by orientation x 90 degrees). `orientation` is omitted when the peer reports a value outside 0-3.
        - A `: keepalive` comment is written every 15 s.

        **Rules:**
        - Authenticate with the header `token` or, from `EventSource`, the query string `?token=`.
        - **409** and **404** are returned as JSON before the stream starts; after that the response is always **200** with `text/event-stream`.
        - Several subscribers can listen to the same call; a slow subscriber misses events rather than delaying others.
        - There is no initial snapshot: the first event arrives on the next change.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of the live call.
          schema:
            type: string
          example: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Event stream opened.
          content:
            text/event-stream:
              schema:
                type: string
                description: SSE stream; every `data:` line is a JSON video state object.
                example: "retry: 3000


                  data: {\"active\":true,\"orientation\":0,\"raw\":1,\"upgrade\":false}

                  \n"
              example: "retry: 3000


                data: {\"active\":true,\"orientation\":0,\"raw\":1,\"upgrade\":false}


                : keepalive


                data: {\"active\":false,\"raw\":0,\"upgrade\":false}

                \n"
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this ID for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/{call_id}/video/stats:
    get:
      tags:
        - Call
      summary: Get live video telemetry for a call
      description: |
        Returns counters of the video WebSocket attached to a live call while it runs: cameras received, frames
        received versus delivered and shed under backpressure, backlog size, and the outbound pipeline. Use it to
        tell server-side backpressure apart from client-side decoding lag.

        **Rules:**
        - Only available while a `/call/{call_id}/video/stream` socket is connected; otherwise **404** `no live video stream for this call`.
        - A call ID of another instance also answers **404**, exactly like a call without a video socket.
        - Rates (`framesPerSecond`) are averages since the socket opened, not instantaneous values.
        - `outbound.mediaReadyAfterSeconds` only appears once the first outbound frame was accepted.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of a live call with a connected video socket.
          schema:
            type: string
          example: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Current video telemetry.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallVideoStatsResponse"
              example:
                code: 200
                data:
                  callId: "call_17B3F2A1C9D04E8F"
                  framing: participant
                  uptimeSeconds: 42.7
                  inbound:
                    participants: 1
                    framesReceived: 1260
                    framesDelivered: 1248
                    framesShed: 12
                    participantsRejected: 0
                    backlogBytes: 0
                    peakBacklogBytes: 184320
                    participantCap: 32
                    framesPerSecond: 29.2
                    streams:
                      - id: "123456789012345@lid"
                        frames: 1260
                        bytes: 5120344
                        orientation: 0
                        ssrc: 3405691582
                        secondsSinceLastFrame: 0.03
                        secondsActive: 42.1
                        fps: 29.9
                  outbound:
                    framesSent: 1190
                    bytesSent: 4388120
                    framesDropped: 35
                    framesOversize: 0
                    keyframeRequests: 2
                    mediaReady: true
                    mediaReadyAfterSeconds: 1.8
                    framesPerSecond: 27.9
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: the call is not live for this instance, or no video socket is connected to it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: no live video stream for this call
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/{call_id}/events:
    get:
      tags:
        - Call
      summary: Stream in-call events (Server-Sent Events)
      description: |
        Opens a Server-Sent Events stream with everything that happens inside a live call: reactions, raised hands,
        screen sharing, mute state, group roster and waiting-room changes, and call phase transitions. Reactions
        exist only as events (there is nothing to poll), which is why this is a stream.

        **Event format:**
        - The stream starts with `retry: 3000`, then one `data:` line per event with a JSON object carrying `seq` and `type`.
        - `reaction`: `emoji`, `participant`, `participantId`, `device`, `removed` (true when the reaction was withdrawn).
        - `hand`: `participant`, `raised`. `screenshare`: `participant`, `active`.
        - `mute`: `muted`; it reports this account's own mute state and carries no participant.
        - `group-state` and `waiting-room`: `state` holds the engine roster object verbatim (Go field names such as `TransactionID` and `Participants`).
        - `phase`: `phase` is `idle`, `calling`, `ringing`, `connecting`, `active`, `ended` or `unknown`.
        - A `: keepalive` comment is written every 15 s.

        **Rules:**
        - Authenticate with the header `token` or, from `EventSource`, the query string `?token=`.
        - On connect, the last known mute, hand and screen-share states are replayed first with `seq: 0`; live events then use an increasing `seq` shared by all subscribers of the call.
        - The first subscriber also receives the cached group roster and waiting room right away, when the call has them.
        - A subscriber that falls 64 events behind misses events instead of slowing the call down.
        - **409** and **404** are JSON responses sent before the stream starts; **500** `streaming unsupported` when the connection cannot be flushed.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of the live call.
          schema:
            type: string
          example: "call_17B3F2A1C9D04E8F"
      responses:
        "200":
          description: Event stream opened.
          content:
            text/event-stream:
              schema:
                type: string
                description: SSE stream; every `data:` line is a JSON call event.
                example: "retry: 3000


                  data: {\"seq\":1,\"type\":\"phase\",\"phase\":\"active\"}

                  \n"
              example: "retry: 3000


                data: {\"seq\":0,\"type\":\"hand\",\"participant\":\"123456789012345@lid\",\"raised\":true}


                data: {\"seq\":7,\"type\":\"reaction\",\"participant\":\"123456789012346@lid\",\"participantId\":\"2\",\"emoji\":\"👍\"}


                data: {\"seq\":8,\"type\":\"phase\",\"phase\":\"ended\"}

                \n"
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this ID for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the native calls engine is not running for this instance, or the network route blocks calls."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false
        "500":
          description: "Internal Server Error: the HTTP connection does not support streaming (for example behind a buffering proxy)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: streaming unsupported
                success: false

  /call/{call_id}/group/state:
    get:
      tags:
        - Call
      summary: Get the roster of a live group call
      description: |
        Returns the authoritative participant roster WhatsApp reported for a live group call: who is in it, the
        phone number when known, and each participant's state.

        **Rules:**
        - Only group calls have a roster: a 1:1 call answers **409** `this call has no group roster`.
        - `state` is the raw WhatsApp roster state, for example `connected`, `outgoing`, `receipt` or `invited`.
        - `phone` is omitted when WhatsApp did not provide the phone number for a LID participant.
        - For push updates instead of polling, listen to `group-state` events on `GET /call/{call_id}/events`.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of a live group call.
          schema:
            type: string
          example: "call_5C1E9A7B3D2F4E60"
      responses:
        "200":
          description: Current group roster.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallGroupStateResponse"
              example:
                code: 200
                data:
                  callId: "call_5C1E9A7B3D2F4E60"
                  transactionId: 4
                  participants:
                    - jid: "123456789012340@lid"
                      state: connected
                    - jid: "123456789012345@lid"
                      phone: "5511988888888@s.whatsapp.net"
                      state: connected
                    - jid: "123456789012346@lid"
                      phone: "5511977777777@s.whatsapp.net"
                      state: outgoing
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this ID for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the engine is not running (or the network route blocks calls), or the call has no group roster."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_roster:
                  summary: 1:1 call without a group roster
                  value:
                    code: 409
                    error: "voip: not available for this call: this call has no group roster"
                    success: false
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /call/{call_id}/waiting-room:
    get:
      tags:
        - Call
      summary: Get the waiting room of a call-link call
      description: |
        Returns the waiting-room state of a live call-link call: whether approval is on, whether this account is an
        admin or is itself waiting, and the users waiting for admission.

        **Rules:**
        - Only call-link calls have a waiting room: other calls answer **409** `this call has no waiting room`.
        - Admit or deny the listed `users` with `POST /call/waiting-room/admit` and `/call/waiting-room/deny` (admins only).
        - `inWaitingRoom: true` means this account joined a link and is still waiting to be admitted.
        - For push updates, listen to `waiting-room` events on `GET /call/{call_id}/events`.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: call_id
          in: path
          required: true
          description: Engine call ID of a live call-link call.
          schema:
            type: string
          example: "call_8D4B2E6A1C9F3E70"
      responses:
        "200":
          description: Current waiting-room state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallWaitingRoomResponse"
              example:
                code: 200
                data:
                  callId: "call_8D4B2E6A1C9F3E70"
                  enabled: true
                  isAdmin: true
                  inWaitingRoom: false
                  transactionId: 2
                  users:
                    - jid: "123456789012346@lid"
                      phone: "5511977777777@s.whatsapp.net"
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: no live call with this ID for this instance."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: call not found
                success: false
        "409":
          description: "Conflict: the engine is not running (or the network route blocks calls), or the call has no waiting room."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_waiting_room:
                  summary: Call without a waiting room
                  value:
                    code: 409
                    error: "voip: not available for this call: this call has no waiting room"
                    success: false
                engine_not_enabled:
                  summary: Calls engine not enabled
                  value:
                    code: 409
                    error: calls engine not enabled for this instance (set calls_enabled and reconnect)
                    success: false
                tcp_only_route:
                  summary: Mobile TCP-only network route blocks calls
                  value:
                    code: 409
                    error: calls_disabled_mobile_tcp_only_route
                    success: false

  /chat/reply:
    post:
      tags:
        - Chat
      summary: Reply to a message with quoted text
      description: |
        Sends a text message that quotes a previous message, built with the library's reply helper so it shows as a reply in the chat.

        **Rules:**
        - `to`, `quoted_msg_id` and `content_type` are required. `to` is the chat of both the reply and the quoted message (phone number, user JID or group JID).
        - Only `content_type: "text"` is supported, and then `content` is required. `image`, `video`, `document` and `audio` return **400** pointing to the matching `/chat/send/*` endpoint; any other value returns **400** `unsupported content_type`.
        - `quoted_sender` is the author of the quoted message (needed in groups). When omitted or unparsable it defaults to `to`. `quoted_from_me` marks the quoted message as sent by this account.
        - The quote preview carries the placeholder text `(quoted message)`, not the original content.
        - The WhatsApp client must be connected (**500** `client not connected` otherwise); the connection check runs before the body is read.
        - For media replies use `/chat/send/{image,video,document,audio}` with `ContextInfo.stanzaID` and `ContextInfo.participant`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatReplyRequest"
            examples:
              reply_text_dm:
                summary: Reply to a received message in a direct chat
                description: "`quoted_sender` is omitted, so the author defaults to `to`."
                value:
                  to: 5511999999999@s.whatsapp.net
                  quoted_msg_id: 3EB0F2A1C9D04E8F1234
                  quoted_from_me: false
                  content_type: text
                  content: Perfect, confirmed for tomorrow at 10 AM.
              reply_in_group:
                summary: Reply in a group quoting another member
                description: "`quoted_sender` identifies the author of the quoted group message."
                value:
                  to: 120363000000000011@g.us
                  quoted_msg_id: 3EB0F2A1C9D04E8F5678
                  quoted_from_me: false
                  quoted_sender: 5511888888888@s.whatsapp.net
                  content_type: text
                  content: I agree with the proposal above.
              reply_to_own_message:
                summary: Reply to a message this account sent
                description: "`quoted_from_me: true` marks the quoted message as your own."
                value:
                  to: "5511999999999"
                  quoted_msg_id: 3EB0C767D26A1D6B8A9F
                  quoted_from_me: true
                  content_type: text
                  content: Adding one more detail to my previous message.
      responses:
        "200":
          description: The reply was sent and acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatReplyResponse"
              example:
                code: 200
                data:
                  success: true
                  message_id: 3EB0C767D26A1D6B8AA0
                  timestamp: "2026-09-15T10:30:00-03:00"
                  to: 5511999999999@s.whatsapp.net
                  quoted_msg_id: 3EB0F2A1C9D04E8F1234
                  content_type: text
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, a required field is missing, `to` is invalid, or `content_type`/`content` is not supported."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_request_body:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode request body
                    success: false
                missing_fields:
                  summary: to, quoted_msg_id or content_type missing
                  value:
                    code: 400
                    error: to, quoted_msg_id and content_type are required
                    success: false
                invalid_to:
                  summary: to is not a valid number or JID
                  value:
                    code: 400
                    error: invalid to JID
                    success: false
                missing_content:
                  summary: Text reply without content
                  value:
                    code: 400
                    error: content is required for content_type=text
                    success: false
                media_content_type:
                  summary: Media content type
                  value:
                    code: 400
                    error: content_type="image" not supported in /chat/reply; use /chat/send/image with quoted_msg_id
                    success: false
                unsupported_content_type:
                  summary: Unknown content type
                  value:
                    code: 400
                    error: unsupported content_type "sticker"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the client is not connected, or the reply could not be built or sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                send_failed:
                  summary: Reply could not be sent
                  value:
                    code: 500
                    error: "failed to send reply: server returned error 479"
                    success: false

  /community/link:
    post:
      tags:
        - Community
      summary: Link a group to a community
      description: |
        Adds an existing group to a community (parent group) as a sub-group. The session must be an admin of the community
        and of the group being linked.

        **Rules:**
        - `parent_group` (community JID) and `child_group` (group JID) are required and must both be non-empty.
        - Both values are parsed as JIDs; an unparsable value returns **400** naming the field.
        - Any WhatsApp refusal (not an admin, group already linked elsewhere, not a community) returns the generic **500** `failed to link group`; the reason is only logged.
        - A refusal can arrive **inside a successful IQ**, as `<group error="409">` (already linked) or `<group error="400">`.
          Those are detected and reported as **500** too, so a `200` here means the link really happened. Confirm with
          `GET /group/info` (`LinkedParentJID`) or `POST /community/sub-groups` when the caller needs proof.
        - **Rate limit:** linking and unlinking the same group repeatedly makes WhatsApp answer `429 rate-overlimit`, which also
          surfaces as the generic **500**. The limit is per group — a different group still links normally. Wait a few minutes
          before retrying; the previous state is unchanged.

        **Use Cases:**
        - Bring department groups under one company community
        - Attach a newly created project group to an existing community
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommunityLinkRequest"
            examples:
              link:
                summary: Link a group to a community
                description: The community JID goes in `parent_group`, the group to attach in `child_group`.
                value:
                  parent_group: "120363000000000033@g.us"
                  child_group: "120363000000000011@g.us"
      responses:
        "200":
          description: Group linked; the normalized JIDs are echoed back.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommunityLinkResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Group linked to community successfully
                  parent_group: "120363000000000033@g.us"
                  child_group: "120363000000000011@g.us"
                success: true
        "400":
          description: "Bad Request: invalid JSON, a missing field, or an unparsable JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: parent_group or child_group missing
                  value:
                    code: 400
                    error: missing parent_group or child_group in payload
                    success: false
                invalid_parent:
                  summary: parent_group is not a valid JID
                  value:
                    code: 400
                    error: invalid parent group JID
                    success: false
                invalid_child:
                  summary: child_group is not a valid JID
                  value:
                    code: 400
                    error: invalid child group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp refused the link."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                link_failed:
                  summary: WhatsApp refused the link
                  value:
                    code: 500
                    error: failed to link group
                    success: false

  /community/unlink:
    post:
      tags:
        - Community
      summary: Unlink a group from its community
      description: |
        Removes a group from the community it is linked to. The gateway first reads the group's info to find the parent
        community, then sends the unlink request; the group itself keeps existing as a standalone group.

        **Rules:**
        - `group` is required. The parent community is discovered automatically from `LinkedParentJID`.
        - A group that is not linked to any community returns **400** `group is not linked to any community`.
        - Failures while reading the group (not a member, unknown group) return **500** `failed to get group info`; a refused unlink returns **500** `failed to unlink group`.
        - A refusal can arrive inside a successful IQ as `<group error="...">`; it is detected and reported as **500**, so a
          `200` means the group really left the community (`LinkedParentJID` becomes empty).
        - **Rate limit:** repeating link/unlink on the same group makes WhatsApp answer `429 rate-overlimit`, which also surfaces
          as the generic **500**. Wait a few minutes before retrying; the previous state is unchanged.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommunityUnlinkRequest"
            examples:
              unlink:
                summary: Unlink a group
                description: Only the sub-group JID is needed; its parent community is looked up first.
                value:
                  group: "120363000000000011@g.us"
      responses:
        "200":
          description: Group unlinked from its community.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommunityUnlinkResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Group unlinked from community successfully
                  group: "120363000000000011@g.us"
                success: true
        "400":
          description: "Bad Request: invalid JSON, missing or unparsable `group`, or the group has no parent community."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_group:
                  summary: group missing
                  value:
                    code: 400
                    error: missing group in payload
                    success: false
                invalid_group:
                  summary: group is not a valid JID
                  value:
                    code: 400
                    error: invalid group JID
                    success: false
                not_linked:
                  summary: Group has no parent community
                  value:
                    code: 400
                    error: group is not linked to any community
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, the group info query failed, or WhatsApp refused the unlink."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                group_info_failed:
                  summary: Group info could not be read
                  value:
                    code: 500
                    error: failed to get group info
                    success: false
                unlink_failed:
                  summary: WhatsApp refused the unlink
                  value:
                    code: 500
                    error: failed to unlink group
                    success: false

  /community/linked:
    get:
      tags:
        - Community
      summary: List the groups linked to a community (legacy GET)
      description: |
        Compatibility alias of `POST /community/linked-groups` kept for existing clients. Returns the sub-groups of a
        community with their names and the announcement-group flag.

        **Rules:**
        - Pass the community JID in the `community` query parameter. Old clients that send a JSON body `{"community": "..."}` with GET are still accepted; the query parameter wins when both are present.
        - `linked_groups` is `null` (and `count` is `0`) when the community has no sub-groups.
        - WhatsApp verdicts are mapped to stable codes: **403** `community_forbidden` (the session cannot inspect this community), **429** `community_rate_limited` with `Retry-After: 5`, **500** `community_linkage_failed` for anything else.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: community
          in: query
          required: true
          description: Community (parent group) JID. Leading and trailing spaces are trimmed.
          schema:
            type: string
          example: "120363000000000033@g.us"
      responses:
        "200":
          description: Sub-groups of the community.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkedGroupsResponse"
              example:
                code: 200
                data:
                  success: true
                  community: "120363000000000033@g.us"
                  linked_groups:
                    - jid: "120363000000000034@g.us"
                      name: Company Announcements
                      is_default_sub: true
                    - jid: "120363000000000011@g.us"
                      name: Project Team
                      is_default_sub: false
                  count: 2
                success: true
        "400":
          description: "Bad Request: no community given, an invalid JSON body, or an unparsable community JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                missing_community:
                  summary: community missing
                  value:
                    code: 400
                    error: missing community in payload
                    success: false
                could_not_decode_payload:
                  summary: Legacy JSON body is invalid
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                invalid_community:
                  summary: community is not a valid JID
                  value:
                    code: 400
                    error: invalid community JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "403":
          description: "Forbidden: WhatsApp does not allow this session to inspect the community."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 403
                error: community_forbidden
                success: false
        "429":
          description: "Too Many Requests: WhatsApp rate-limited the community query. Retry after the `Retry-After` interval."
          headers:
            Retry-After:
              description: Seconds to wait before retrying. Always `5`.
              schema:
                type: integer
                example: 5
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 429
                error: community_rate_limited
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or any other failure of the community query."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                linkage_failed:
                  summary: Community query failed
                  value:
                    code: 500
                    error: community_linkage_failed
                    success: false

  /community/linked-groups:
    post:
      tags:
        - Community
      summary: List the groups linked to a community
      description: |
        Returns the sub-groups of a community (parent group), including its announcement group, with their names.
        This is the preferred route; `POST /community/sub-groups` and `GET /community/linked` are aliases of the same handler.

        **Rules:**
        - Send `community` in the JSON body. A `community` query parameter is also accepted and takes precedence over the body.
        - `is_default_sub: true` marks the community announcement group.
        - `linked_groups` is `null` (and `count` is `0`) when the community has no sub-groups.
        - **403** `community_forbidden` when the session cannot inspect the community; **429** `community_rate_limited` with `Retry-After: 5`; **500** `community_linkage_failed` for other WhatsApp failures.

        **Use Cases:**
        - Find the announcement group JID of a community
        - Show the group directory of a community in a dashboard
      security:
        - ApiKeyAuth: []
      parameters:
        - name: community
          in: query
          required: false
          description: Optional community JID. When present it overrides `community` from the body.
          schema:
            type: string
          example: "120363000000000033@g.us"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LinkedGroupsRequest"
            examples:
              community:
                summary: List the sub-groups of a community
                description: Pass the community (parent group) JID.
                value:
                  community: "120363000000000033@g.us"
      responses:
        "200":
          description: Sub-groups of the community.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkedGroupsResponse"
              examples:
                with_groups:
                  summary: Community with sub-groups
                  value:
                    code: 200
                    data:
                      success: true
                      community: "120363000000000033@g.us"
                      linked_groups:
                        - jid: "120363000000000034@g.us"
                          name: Company Announcements
                          is_default_sub: true
                        - jid: "120363000000000011@g.us"
                          name: Project Team
                          is_default_sub: false
                      count: 2
                    success: true
                empty:
                  summary: Community without sub-groups
                  value:
                    code: 200
                    data:
                      success: true
                      community: "120363000000000033@g.us"
                      linked_groups: null
                      count: 0
                    success: true
        "400":
          description: "Bad Request: invalid JSON, missing `community`, or an unparsable community JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_community:
                  summary: community missing
                  value:
                    code: 400
                    error: missing community in payload
                    success: false
                invalid_community:
                  summary: community is not a valid JID
                  value:
                    code: 400
                    error: invalid community JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "403":
          description: "Forbidden: WhatsApp does not allow this session to inspect the community."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 403
                error: community_forbidden
                success: false
        "429":
          description: "Too Many Requests: WhatsApp rate-limited the community query. Retry after the `Retry-After` interval."
          headers:
            Retry-After:
              description: Seconds to wait before retrying. Always `5`.
              schema:
                type: integer
                example: 5
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 429
                error: community_rate_limited
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or any other failure of the community query."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                linkage_failed:
                  summary: Community query failed
                  value:
                    code: 500
                    error: community_linkage_failed
                    success: false

  /community/sub-groups:
    post:
      tags:
        - Community
      summary: List the sub-groups of a community (alias)
      description: |
        Alias of `POST /community/linked-groups`: same handler, same request and same response. Prefer
        `/community/linked-groups` in new integrations.

        **Rules:**
        - `community` comes from the JSON body, or from the `community` query parameter, which wins when both are set.
        - The response lists each sub-group with `jid`, `name` and `is_default_sub`; `linked_groups` is `null` when there are none.
        - Rate limiting returns **429** with `Retry-After: 5`; a forbidden community returns **403**.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: community
          in: query
          required: false
          description: Optional community JID. When present it overrides `community` from the body.
          schema:
            type: string
          example: "120363000000000033@g.us"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LinkedGroupsRequest"
            examples:
              community:
                summary: List the sub-groups of a community
                description: Pass the community (parent group) JID.
                value:
                  community: "120363000000000033@g.us"
      responses:
        "200":
          description: Sub-groups of the community.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkedGroupsResponse"
              example:
                code: 200
                data:
                  success: true
                  community: "120363000000000033@g.us"
                  linked_groups:
                    - jid: "120363000000000034@g.us"
                      name: Company Announcements
                      is_default_sub: true
                  count: 1
                success: true
        "400":
          description: "Bad Request: invalid JSON, missing `community`, or an unparsable community JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_community:
                  summary: community missing
                  value:
                    code: 400
                    error: missing community in payload
                    success: false
                invalid_community:
                  summary: community is not a valid JID
                  value:
                    code: 400
                    error: invalid community JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "403":
          description: "Forbidden: WhatsApp does not allow this session to inspect the community."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 403
                error: community_forbidden
                success: false
        "429":
          description: "Too Many Requests: WhatsApp rate-limited the community query. Retry after the `Retry-After` interval."
          headers:
            Retry-After:
              description: Seconds to wait before retrying. Always `5`.
              schema:
                type: integer
                example: 5
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 429
                error: community_rate_limited
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or any other failure of the community query."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                linkage_failed:
                  summary: Community query failed
                  value:
                    code: 500
                    error: community_linkage_failed
                    success: false

  /community/requests:
    post:
      tags:
        - Community
      summary: List pending join requests of a group
      description: |
        Returns the people waiting for admin approval to join a group or community (groups with **Approve new members**
        turned on, see `POST /group/joinapproval`). The session must be an admin.

        **Rules:**
        - `group` is required and accepts a group or community JID.
        - Only the requester JID is returned; the request time reported by WhatsApp is not included.
        - `pending_requests` is `null` (and `count` is `0`) when nobody is waiting.
        - Any WhatsApp failure returns the generic **500** `failed to get pending requests`.

        **Use Cases:**
        - Review join requests before approving them with `POST /community/requests/update`
        - Alert moderators when requests are waiting
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupRequestsRequest"
            examples:
              group:
                summary: Pending requests of a group
                description: Pass the group (or community) JID.
                value:
                  group: "120363000000000011@g.us"
      responses:
        "200":
          description: Pending join requests.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupRequestsResponse"
              examples:
                pending:
                  summary: Two people waiting
                  value:
                    code: 200
                    data:
                      success: true
                      group: "120363000000000011@g.us"
                      pending_requests:
                        - jid: 123456789012345@lid
                        - jid: 5511988888888@s.whatsapp.net
                      count: 2
                    success: true
                none:
                  summary: Nobody waiting
                  value:
                    code: 200
                    data:
                      success: true
                      group: "120363000000000011@g.us"
                      pending_requests: null
                      count: 0
                    success: true
        "400":
          description: "Bad Request: invalid JSON, or `group` missing or unparsable."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_group:
                  summary: group missing
                  value:
                    code: 400
                    error: missing group in payload
                    success: false
                invalid_group:
                  summary: group is not a valid JID
                  value:
                    code: 400
                    error: invalid group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the WhatsApp query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: WhatsApp query failed
                  value:
                    code: 500
                    error: failed to get pending requests
                    success: false

  /community/requests/update:
    post:
      tags:
        - Community
      summary: Approve or reject pending join requests
      description: |
        Approves or rejects people waiting to join a group or community. The session must be an admin.

        **Rules:**
        - `group`, `action` and a non-empty `requests` array are required.
        - `action` is case-insensitive: `approve` or `accept` approve; `reject` or `deny` reject. Anything else returns **400**.
        - Every entry of `requests` must parse as a JID (phone numbers are accepted); the first invalid one aborts with **400**.
        - `link_code` is accepted but not used.
        - **200 does not confirm each request.** WhatsApp answers per person, but that list is discarded; check again with `POST /community/requests`.
        - `data.message` is built as `Successfully <action>ed <n> request(s)` from the raw `action`, so `approve` reads `approveed`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommunityRequestsUpdateRequest"
            examples:
              approve:
                summary: Approve two requests
                description: Requesters can be given as LIDs, phone JIDs or phone numbers.
                value:
                  group: "120363000000000011@g.us"
                  action: approve
                  requests:
                    - 123456789012345@lid
                    - 5511988888888@s.whatsapp.net
              reject:
                summary: Reject one request
                description: The requester is not added to the group.
                value:
                  group: "120363000000000011@g.us"
                  action: reject
                  requests:
                    - 5511988888888@s.whatsapp.net
      responses:
        "200":
          description: WhatsApp accepted the update. `action` and `requests` echo the request as sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommunityRequestsUpdateResponse"
              examples:
                approved:
                  summary: Two requests approved
                  value:
                    code: 200
                    data:
                      success: true
                      message: Successfully approveed 2 request(s)
                      group: "120363000000000011@g.us"
                      action: approve
                      requests:
                        - 123456789012345@lid
                        - 5511988888888@s.whatsapp.net
                    success: true
                rejected:
                  summary: One request rejected
                  value:
                    code: 200
                    data:
                      success: true
                      message: Successfully rejected 1 request(s)
                      group: "120363000000000011@g.us"
                      action: reject
                      requests:
                        - 5511988888888@s.whatsapp.net
                    success: true
        "400":
          description: "Bad Request: invalid JSON, missing fields, an unparsable JID, or an unknown `action`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: group, action or requests missing
                  value:
                    code: 400
                    error: missing group, action or requests in payload
                    success: false
                invalid_group:
                  summary: group is not a valid JID
                  value:
                    code: 400
                    error: invalid group JID
                    success: false
                invalid_request_jid:
                  summary: A requests entry is not a valid JID
                  value:
                    code: 400
                    error: "invalid JID: @s.whatsapp.net"
                    success: false
                invalid_action:
                  summary: action not accepted
                  value:
                    code: 400
                    error: "invalid action. Use: approve or reject"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp refused the update."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: WhatsApp refused the update
                  value:
                    code: 500
                    error: failed to update group requests
                    success: false

  /community/create:
    post:
      tags:
        - Community
      summary: Create a community
      description: |
        Creates a new community (parent group) with the session as owner, then tries to link the given groups to it.

        **Rules:**
        - `name` is required. `description` is **not sent to WhatsApp**; it is only echoed in the response. Set it afterwards with `POST /group/topic` on the community JID.
        - `groups` is optional. Each entry is linked one by one after creation; unparsable or refused groups are skipped silently, so compare `data.community.linked_groups` with the request.
        - `linked_groups` is `null` when no group was linked.
        - A failed creation returns the generic **500** `failed to create community`; link failures never fail the request.

        **Use Cases:**
        - Create a company community and attach existing department groups in one call
        - Start an empty community and add groups later with `POST /community/link`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCommunityRequest"
            examples:
              with_groups:
                summary: Community with two existing groups
                description: Groups the session is not allowed to link are skipped without an error.
                value:
                  name: Acme Company
                  description: Official community of Acme employees
                  groups:
                    - "120363000000000011@g.us"
                    - "120363000000000012@g.us"
              name_only:
                summary: Empty community
                description: Only the name is required.
                value:
                  name: Acme Company
      responses:
        "200":
          description: Community created. `linked_groups` lists only the groups that were actually linked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateCommunityResponse"
              examples:
                with_groups:
                  summary: One of two groups linked
                  value:
                    code: 200
                    data:
                      success: true
                      message: Community created successfully
                      community:
                        jid: "120363000000000033@g.us"
                        name: Acme Company
                        description: Official community of Acme employees
                        linked_groups:
                          - "120363000000000011@g.us"
                    success: true
                name_only:
                  summary: Empty community
                  value:
                    code: 200
                    data:
                      success: true
                      message: Community created successfully
                      community:
                        jid: "120363000000000033@g.us"
                        name: Acme Company
                        description: ""
                        linked_groups: null
                    success: true
        "400":
          description: "Bad Request: invalid JSON or missing `name`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_name:
                  summary: name missing
                  value:
                    code: 400
                    error: missing name in payload
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp refused to create the community."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                create_failed:
                  summary: Creation refused
                  value:
                    code: 500
                    error: failed to create community
                    success: false

  /community/announcement:
    post:
      tags:
        - Community
      summary: Send a text announcement to a community
      description: |
        Sends a plain text message to the JID given in `community` and returns the WhatsApp message ID.

        **Rules:**
        - `community` and `message` are required.
        - The message goes to exactly that JID. To post in the community announcement group, pass the JID of the linked group with `is_default_sub: true` (see `POST /community/linked-groups`).
        - `linked_groups`, `background_color` and `font` are accepted but **ignored**: no per-group fan-out and no styling are applied.
        - A send failure returns the generic **500** `failed to send announcement`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommunityAnnouncementRequest"
            examples:
              announcement_group:
                summary: Post in the announcement group
                description: Uses the JID of the community's default announcement sub-group.
                value:
                  community: "120363000000000034@g.us"
                  message: "Office closed on Friday for maintenance."
      responses:
        "200":
          description: Message accepted by WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommunityAnnouncementResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Announcement sent successfully
                  community: "120363000000000034@g.us"
                  message_id: 3EB0C767D26A1D6B8A9F
                  timestamp: "2026-09-15T12:00:00Z"
                success: true
        "400":
          description: "Bad Request: invalid JSON, missing fields, or an unparsable community JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: community or message missing
                  value:
                    code: 400
                    error: missing community or message in payload
                    success: false
                invalid_community:
                  summary: community is not a valid JID
                  value:
                    code: 400
                    error: invalid community JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the message could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: failed to send announcement
                    success: false

  /business/resolve-link:
    post:
      tags:
        - Business
      summary: Resolve a business message link
      description: |
        Resolves a WhatsApp business message link (`wa.me/message/…`) and returns the business behind it plus the pre-filled message.

        **Features:**
        - `business_info.jid`: business JID
        - `business_info.verified_name` and `verified_level`: verified business name and level (`unknown` when not verified)
        - `business_info.message`: text WhatsApp pre-fills in the input box when the link is opened

        **Rules:**
        - `link` is required (`400 missing link in payload`); a body that is not JSON returns `400 could not decode payload`
        - Unknown or expired links return `500 failed to resolve link`

        **Use Cases:**
        - Validate a business link before showing it to users
        - Discover which account a campaign link opens
      requestBody:
        required: true
        description: Link to resolve.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResolveBusinessLinkRequest"
            examples:
              full_link:
                summary: Full wa.me link
                description: Resolves a business message link copied from WhatsApp.
                value:
                  link: https://wa.me/message/ABCDEFGHIJKLM1
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Business behind the link
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Resolution result.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          business_info:
                            type: object
                            description: Business resolved from the link.
                            properties:
                              jid:
                                type: string
                                description: Business JID.
                                example: 5511988888888@s.whatsapp.net
                              verified_name:
                                type: string
                                description: Verified business name (empty when not verified).
                                example: Example Store
                              verified_level:
                                type: string
                                description: Verification level reported by WhatsApp.
                                example: unknown
                              message:
                                type: string
                                description: Message pre-filled when the link is opened.
                                example: Hello! I would like to see the catalog.
              examples:
                resolved:
                  summary: Link resolved
                  value:
                    code: 200
                    data:
                      success: true
                      business_info:
                        jid: 5511988888888@s.whatsapp.net
                        verified_name: Example Store
                        verified_level: unknown
                        message: Hello! I would like to see the catalog.
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `missing link in payload`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                link_missing:
                  summary: link missing
                  value:
                    code: 400
                    error: missing link in payload
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, or WhatsApp could not resolve the link.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                unknown_or_expired_link:
                  summary: Unknown or expired link
                  value:
                    code: 500
                    error: failed to resolve link
                    success: false

  /business/contact-qr:
    post:
      tags:
        - Business
      summary: Get or rotate the own contact QR link
      description: |
        Returns the contact QR link (`wa.me/qr/…`) of the connected account, optionally revoking the current one first.

        **Features:**
        - `revoked: false` (default) returns the current link
        - `revoked: true` revokes the current link (IQ action `revoke`) and returns the new one; old printed QR codes stop working

        **Rules:**
        - The body must be a JSON object; an empty body returns `400 could not decode payload` (send `{}`)
        - `phone` is ignored: the link always belongs to the connected account (`jid` in the response)
      requestBody:
        required: true
        description: Whether to rotate the link.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactQRRequest"
            examples:
              current:
                summary: Get current link
                description: Returns the existing contact QR link.
                value:
                  revoked: false
              rotate:
                summary: Revoke and create a new link
                description: Invalidates the previous QR code and returns a new link.
                value:
                  revoked: true
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Contact QR link
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: QR link of the connected account.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          jid:
                            type: string
                            description: JID of the connected account that owns the link.
                            example: 5511999999999:12@s.whatsapp.net
                          link:
                            type: string
                            description: Contact QR link.
                            example: https://wa.me/qr/ABCDEFGHIJKLM1
                          revoked:
                            type: boolean
                            description: Echo of the `revoked` flag sent in the request.
                            example: false
              examples:
                current:
                  summary: Current link
                  value:
                    code: 200
                    data:
                      success: true
                      jid: 5511999999999:12@s.whatsapp.net
                      link: https://wa.me/qr/ABCDEFGHIJKLM1
                      revoked: false
                    success: true
                rotated:
                  summary: Link rotated
                  value:
                    code: 200
                    data:
                      success: true
                      jid: 5511999999999:12@s.whatsapp.net
                      link: https://wa.me/qr/NOPQRSTUVWXYZ2
                      revoked: true
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_invalid_body:
                  summary: Empty or invalid body
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, or the QR IQ failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                iq_failed:
                  summary: IQ failed
                  value:
                    code: 500
                    error: failed to get QR link
                    success: false

  /business/bots:
    get:
      tags:
        - Business
      summary: List available WhatsApp bots
      description: |
        Lists the AI bots (Meta AI personas) that WhatsApp makes available to the connected account.

        **Features:**
        - `bots[]` with `bot_jid` and `persona_id`
        - `count` with the number of bots

        **Rules:**
        - No body or query parameters
        - `bots` is `null` when WhatsApp returns no bots
        - Use the `bot_jid` values with `POST /business/bot-profiles`
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Bot list
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Available bots.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          count:
                            type: integer
                            description: Number of bots.
                            example: 1
                          bots:
                            type: array
                            nullable: true
                            description: Bots, or null when none.
                            items:
                              type: object
                              properties:
                                bot_jid:
                                  type: string
                                  description: Bot JID (`@bot`).
                                  example: 867051314767696@bot
                                persona_id:
                                  type: string
                                  description: Persona ID of the bot.
                                  example: "1234567890123456"
              examples:
                one_bot:
                  summary: One bot available
                  value:
                    code: 200
                    data:
                      bots:
                        - bot_jid: 867051314767696@bot
                          persona_id: "1234567890123456"
                      count: 1
                      success: true
                    success: true
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, or the bot list IQ failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                iq_failed:
                  summary: IQ failed
                  value:
                    code: 500
                    error: failed to get bot list
                    success: false

  /business/bot-profiles:
    post:
      tags:
        - Business
      summary: Get bot profiles
      description: |
        Returns profile details (name, description, category and commands) for bots listed by `GET /business/bots`.

        **Rules:**
        - `bot_jids` is required (`400 missing bot_jids in payload`)
        - The gateway first loads the bot list; each JID must parse (`400 invalid JID: <value>`) and only JIDs present in the list are queried
        - When none of the JIDs is in the list the route returns `404 no matching bots found`
      requestBody:
        required: true
        description: Bot JIDs to describe.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BotProfilesRequest"
            examples:
              one_bot:
                summary: One bot
                description: Describes a single bot from the bot list.
                value:
                  bot_jids:
                    - 867051314767696@bot
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Bot profiles
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Profiles of the matching bots.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          count:
                            type: integer
                            description: Number of profiles returned.
                            example: 1
                          profiles:
                            type: array
                            description: Bot profiles.
                            items:
                              type: object
                              properties:
                                jid:
                                  type: string
                                  description: Bot JID.
                                  example: 867051314767696@bot
                                name:
                                  type: string
                                  description: Bot name.
                                  example: Meta AI
                                description:
                                  type: string
                                  description: Bot description.
                                  example: Ask me anything.
                                category:
                                  type: string
                                  description: Bot category.
                                  example: Assistant
                                commands:
                                  type: array
                                  description: Slash commands (keys are capitalized).
                                  items:
                                    type: object
                                    properties:
                                      Name:
                                        type: string
                                        description: Command.
                                        example: /imagine
                                      Description:
                                        type: string
                                        description: What the command does.
                                        example: Create an image from a prompt
              examples:
                one_profile:
                  summary: One bot profile
                  value:
                    code: 200
                    data:
                      count: 1
                      success: true
                      profiles:
                        - jid: 867051314767696@bot
                          name: Meta AI
                          description: Ask me anything.
                          category: Assistant
                          commands:
                            - Name: /imagine
                              Description: Create an image from a prompt
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `missing bot_jids in payload`; `invalid JID: bot@@invalid`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                bot_jids_missing:
                  summary: bot_jids missing
                  value:
                    code: 400
                    error: missing bot_jids in payload
                    success: false
                unparseable_jid:
                  summary: Unparseable JID
                  value:
                    code: 400
                    error: "invalid JID: bot@@invalid"
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "404":
          description: None of the requested JIDs is in the bot list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_matching_bots:
                  summary: No matching bots
                  value:
                    code: 404
                    error: no matching bots found
                    success: false
        "500":
          description: No session, or a bot IQ failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                bot_list_failed:
                  summary: Bot list failed
                  value:
                    code: 500
                    error: failed to get bot list
                    success: false
                profiles_failed:
                  summary: Profiles failed
                  value:
                    code: 500
                    error: failed to get bot profiles
                    success: false

  /business/order/send:
    post:
      tags:
        - Business
      summary: Send an order message to a seller
      description: |
        Sends an order message (`OrderMessage`) to a seller's chat, either as a legacy summary or as a real order registered in the seller's catalog.

        **Modes:**
        - **Legacy summary** (default): sends `order_items`, `order_message` and optional free-form `order_id` without registering anything
        - **`create_order: true`**: registers a real catalog order (1-100 unique lines, `price_amount1000`, one uppercase currency) and sends the server-issued order
        - **`resend_registered_order: true`**: queries an existing order by `order_id` + `order_token` and sends it once, without creating another

        **Rules:**
        - `business_jid` is required, and `order_items` unless resending (`400 missing business_jid or order_items`)
        - `create_order` and `resend_registered_order` are mutually exclusive; `create_order` must not include `order_id`; resending needs `order_id` and `order_token`
        - Registration failures return `502`; when the order exists but the message failed, the `502` body is the recovery object (no envelope) with `registered_order` — do not retry as a new order
        - Legacy send failures return `500 failed to send order`
      requestBody:
        required: true
        description: Seller, order lines and mode.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessOrderSendRequest"
            examples:
              create_order:
                summary: Register a real order
                description: Registers one catalog line with explicit thousandths price and sends the server-issued order.
                value:
                  business_jid: 5511988888888@s.whatsapp.net
                  create_order: true
                  order_message: Order for one wooden chair
                  order_items:
                    - product_id: "7123456789012345"
                      name: Wooden chair
                      quantity: 1
                      currency: BRL
                      price_amount1000: 3000000
              legacy_summary:
                summary: Legacy order summary
                description: Sends an order summary without registering it in the catalog.
                value:
                  business_jid: 5511988888888@s.whatsapp.net
                  order_message: Test order without payment
                  order_items:
                    - product_id: "7123456789012345"
                      name: Wooden chair
                      quantity: 1
                      price: "3000.00"
                      currency: BRL
              resend:
                summary: Resend a registered order
                description: Sends an existing registered order again using its ID and token.
                value:
                  business_jid: 5511988888888@s.whatsapp.net
                  resend_registered_order: true
                  order_id: "1976123456789012"
                  order_token: AR6example0rderT0kenBase64==
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Order message sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        description: Order send result.
                        allOf:
                          - $ref: "#/components/schemas/BusinessOrderSendResult"
              examples:
                registered:
                  summary: Registered order sent
                  value:
                    code: 200
                    data:
                      success: true
                      order_created: true
                      order_reused: false
                      order_kind: registered_order
                      message: Order sent successfully
                      business_jid: 5511988888888@s.whatsapp.net
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      order_details:
                        item_count: 1
                        order_id: "1976123456789012"
                      registered_order:
                        order_id: "1976123456789012"
                        token: AR6example0rderT0kenBase64==
                        price:
                          subtotal_amount: "3000000"
                          total_amount: "3000000"
                          currency: BRL
                          price_status: PROVIDED
                    success: true
                legacy:
                  summary: Legacy summary sent
                  value:
                    code: 200
                    data:
                      success: true
                      order_created: false
                      order_reused: false
                      order_kind: legacy_summary
                      message: Order sent successfully
                      business_jid: 5511988888888@s.whatsapp.net
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      order_details:
                        item_count: 1
                        order_id: ""
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `create_order and resend_registered_order are mutually exclusive`; `registered order ID and token required`; `missing business_jid or order_items`; `invalid business JID`; `real order requires non-negative price_amount1000`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                both_modes:
                  summary: Both modes
                  value:
                    code: 400
                    error: create_order and resend_registered_order are mutually exclusive
                    success: false
                resend_without_id_or_token:
                  summary: Resend without ID or token
                  value:
                    code: 400
                    error: registered order ID and token required
                    success: false
                missing_fields:
                  summary: Missing fields
                  value:
                    code: 400
                    error: missing business_jid or order_items
                    success: false
                invalid_jid:
                  summary: Invalid JID
                  value:
                    code: 400
                    error: invalid business JID
                    success: false
                line_without_price:
                  summary: Line without price
                  value:
                    code: 400
                    error: real order requires non-negative price_amount1000
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, or the legacy order message failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: failed to send order
                    success: false
        "502":
          description: Order registration or lookup failed (gateway envelope), or the order exists but its message failed (recovery body without envelope).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/BusinessOrderRecoveryBody"
              examples:
                profile_unavailable:
                  summary: Seller catalog profile unavailable
                  value:
                    code: 502
                    error: business catalog profile unavailable; order not created
                    success: false
                creation_uncertain:
                  summary: Registration outcome uncertain
                  value:
                    code: 502
                    error: order creation failed; outcome may require verification before retry
                    success: false
                message_send_failed:
                  summary: Order registered, delivery unknown (recovery body)
                  value:
                    success: false
                    order_created: true
                    order_reused: false
                    order_exists: true
                    message_sent: null
                    message_delivery: unknown
                    error: order_created_message_send_failed
                    registered_order:
                      order_id: "1976123456789012"
                      token: AR6example0rderT0kenBase64==
                      price:
                        subtotal_amount: "3000000"
                        total_amount: "3000000"
                        currency: BRL
                        price_status: PROVIDED

  /business/sticker-pack/{packId}:
    get:
      tags:
        - Business
      summary: Fetch sticker pack metadata
      description: |
        Returns the metadata of a public WhatsApp sticker pack plus every sticker with the fields needed to download it through `/chat/downloadsticker`.

        **Features:**
        - Pack header: `id`, `name`, `publisher`, `description`, `file_size`, `image_data_hash`, `animated`, `lottie`, tray image and preview image IDs
        - `stickers[]`: `url`, `direct_path`, `media_key` / `file_hash` / `file_enc_hash` (hex), `file_size`, `mimetype`, dimensions, `emojis`, `accessibility_text`, `handle`
        - `sticker_count` with the number of stickers returned

        **Rules:**
        - `packId` is required (`400 missing packId in path` when blank)
        - Pack IDs come from a received sticker pack message (`stickerPackMessage.stickerPackID`) or from `https://getstickerpack.whatsapp.com/?p=<packId>`
        - **Public packs only.** A pack built in the app ("sticker maker") is not in WhatsApp's public catalog: its
          `stickerPackID` is a local UUID and this route answers **500** `failed to fetch sticker pack: download failed with
          status code 404` (measured live on 2026-09-16). Tell the two apart by the ID shape — public packs use an opaque
          catalog ID, app-made packs a UUID like `73b1c4a8-a09c-4437-930c-4690b2209615`.
        - An app-made pack still arrives complete in the webhook event (`stickerPackMessage` with `directPath`, `mediaKey`,
          `fileSHA256`, `fileEncSHA256` and the sticker list), but its media uses the sticker-pack media key, not the sticker
          one, so posting those fields to `/chat/downloadsticker` fails with `invalid media hmac`. Downloading an app-made pack
          is not exposed by the API today.
      parameters:
        - name: packId
          in: path
          required: true
          description: Public sticker pack ID (usually a 32-character hex string from the share link).
          schema:
            type: string
          example: 8e2a3c1f4b5d6e7f8a9b0c1d2e3f4a5b
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Sticker pack
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        description: Pack metadata and stickers.
                        allOf:
                          - $ref: "#/components/schemas/StickerPack"
              examples:
                pack:
                  summary: Pack with one sticker
                  value:
                    code: 200
                    data:
                      success: true
                      id: 8e2a3c1f4b5d6e7f8a9b0c1d2e3f4a5b
                      name: Cute Cats
                      publisher: Acme Stickers
                      description: A pack of expressive cat stickers.
                      file_size: "1238423"
                      image_data_hash: f31a4c9e8d7b6a5f4e3d2c1b0a9f8e7d
                      animated: 0
                      lottie: 0
                      preview_image_ids:
                        - 1.webp
                        - 2.webp
                      tray_image_id: tray.png
                      tray_image_preview: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
                      stickers:
                        - url: https://mmg.whatsapp.net/v/t62.15575-24/example-sticker.webp
                          direct_path: /v/t62.15575-24/example-sticker.webp
                          media_key: 5f1d0c2b9a8e7f6d5c4b3a2918070605f4e3d2c1b0a998877665544332211000
                          file_hash: a3f1c2d4e5b60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90
                          file_enc_hash: b4e2d3c5f6a70819203b4c5d6e7f8091b2c3d4e5f6a70819203b4c5d6e7f8091
                          file_size: 24576
                          mimetype: image/webp
                          width: 512
                          height: 512
                          emojis:
                            - 😺
                          accessibility_text: Smiling orange cat
                          handle: "01"
                          sticker_hash_without_meta: c5f3e4d6a7b8091a2b3c4d5e6f708192c3d4e5f6a7b8091a2b3c4d5e6f708192
                          preview_webp_id: 1_preview.webp
                      sticker_count: 1
                    success: true
        "400":
          description: "Invalid request: `missing packId in path`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_pack_id:
                  summary: Blank pack ID
                  value:
                    code: 400
                    error: missing packId in path
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "404":
          description: WhatsApp returned no pack for this ID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unknown_pack:
                  summary: Unknown pack
                  value:
                    code: 404
                    error: sticker pack not found
                    success: false
        "500":
          description: No session, or the sticker pack request failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                request_failed:
                  summary: Request failed
                  value:
                    code: 500
                    error: "failed to fetch sticker pack: info query returned status 404: item-not-found"
                    success: false

  /business/order/{orderID}:
    get:
      tags:
        - Business
      summary: Fetch order details
      description: |
        Resolves an order by its ID and token (both present in the received `OrderMessage`) and returns totals and products.

        **Features:**
        - `transport=iq` (default): legacy order lookup over IQ
        - `transport=graphql`: registered buyer order lookup (orders created with `create_order`)
        - Prices are integers in thousandths of the currency

        **Rules:**
        - The session is checked first (`500 no session` / `client not connected`)
        - The token is read from the `X-Order-Token` header, falling back to the legacy `token` query parameter (`400 missing X-Order-Token header or legacy token query parameter`)
        - `transport` other than `iq`/`graphql` returns `400 transport must be iq or graphql`
        - Lookup failures return `500 failed to fetch order: <reason>`; an empty result returns `404 order not found`
      parameters:
        - name: orderID
          in: path
          required: true
          description: Order ID from the received `OrderMessage`.
          schema:
            type: string
          example: "1976123456789012"
        - name: X-Order-Token
          in: header
          required: false
          description: Order token from the `OrderMessage` (preferred; takes precedence over the `token` query parameter).
          schema:
            type: string
          example: AR6example0rderT0kenBase64==
        - name: token
          in: query
          required: false
          description: "Legacy order token. Used only when `X-Order-Token` is absent. Note: the user API token must be sent in the `token` header."
          schema:
            type: string
          example: AR6example0rderT0kenBase64==
        - name: transport
          in: query
          required: false
          description: "`iq` (default, legacy lookup) or `graphql` (registered buyer order)."
          schema:
            type: string
            enum:
              - iq
              - graphql
            default: iq
          example: iq
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Order details
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        description: Order lookup result.
                        allOf:
                          - $ref: "#/components/schemas/OrderDetailsResponse"
              examples:
                order:
                  summary: Order with one product line
                  value:
                    code: 200
                    data:
                      success: true
                      order:
                        id: "1976123456789012"
                        created_at: "2026-09-15T11:30:00Z"
                        catalog_id: "7123456789012300"
                        price:
                          subtotal: 6000000
                          total: 6000000
                          currency: BRL
                          price_status: PROVIDED
                        products:
                          - id: "7123456789012345"
                            name: Wooden chair
                            quantity: 2
                            price: 3000000
                            currency: BRL
                            image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg
                    success: true
        "400":
          description: "Invalid request: `missing X-Order-Token header or legacy token query parameter`; `transport must be iq or graphql`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                token_missing:
                  summary: Token missing
                  value:
                    code: 400
                    error: missing X-Order-Token header or legacy token query parameter
                    success: false
                unknown_transport:
                  summary: Unknown transport
                  value:
                    code: 400
                    error: transport must be iq or graphql
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "404":
          description: WhatsApp returned no order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                order_not_found:
                  summary: Order not found
                  value:
                    code: 404
                    error: order not found
                    success: false
        "500":
          description: "No session, client not connected, or the WhatsApp request failed: `no session`; `client not connected`; `failed to fetch order: info query returned status 404: item-not-found`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                lookup_failed:
                  summary: Lookup failed
                  value:
                    code: 500
                    error: "failed to fetch order: info query returned status 404: item-not-found"
                    success: false

  /business/catalog/{businessJID}:
    get:
      tags:
        - Business
      summary: Get a business product catalog
      description: |
        Reads one page of the public catalog of a business (GraphQL `QueryCatalog`) and returns products in a compact shape.

        **Features:**
        - `catalog.products[]` with ID, retailer ID, name, description, numeric `price` (thousandths, `null` when the product has no usable price), currency, image URLs and availability
        - `catalog.next_cursor` for the next page (omitted on the last page)
        - In-memory cache for 300 s per user, JID, limit and cursor; `cached: true` marks a cache hit

        **Rules:**
        - The session is checked first (`500 no session` / `client not connected`), then the path JID and pagination (`400`)
        - Hidden products and products under Meta review are not returned by this public read; use `GET /business/catalog/own` for the own catalog
        - GraphQL rejections return `500 failed to fetch catalog: upstream query rejected`; other failures return `500 failed to fetch catalog: <reason>`
        - A product without price (or with a non-numeric price) is returned with `price: null`; only a missing upstream page returns `502`
        - `catalog.id` is always empty: WhatsApp does not return a catalog ID on this read
      parameters:
        - name: businessJID
          in: path
          required: true
          description: Business phone number or JID (`5511988888888`, `@s.whatsapp.net` or `@lid`).
          schema:
            type: string
          example: 5511988888888@s.whatsapp.net
        - name: limit
          in: query
          required: false
          description: Page size, 1-100 (default 20). Must appear at most once.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          example: 20
        - name: cursor
          in: query
          required: false
          description: Opaque cursor from `next_cursor` of the previous page. At most once and 2048 bytes.
          schema:
            type: string
            maxLength: 2048
          example: AQHRcursorEXAMPLE
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Catalog page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Catalog read result.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          cached:
                            type: boolean
                            description: True when the page came from the 300 s cache.
                            example: false
                          catalog:
                            type: object
                            description: Catalog page.
                            properties:
                              id:
                                type: string
                                description: Always empty (not returned by WhatsApp).
                                example: ""
                              business_jid:
                                type: string
                                description: Business JID without device.
                                example: 5511988888888@s.whatsapp.net
                              products:
                                type: array
                                description: Products on this page.
                                items:
                                  $ref: "#/components/schemas/BusinessLegacyProduct"
                              next_cursor:
                                type: string
                                description: Cursor for the next page; omitted on the last page.
                                example: AQHRcursorEXAMPLE
              examples:
                fresh:
                  summary: First page from WhatsApp
                  value:
                    code: 200
                    data:
                      success: true
                      cached: false
                      catalog:
                        id: ""
                        business_jid: 5511988888888@s.whatsapp.net
                        products:
                          - id: "7123456789012345"
                            retailer_id: CHAIR-001
                            name: Wooden chair
                            description: Solid wood chair with natural finish.
                            price: 3000000
                            currency: BRL
                            image_urls:
                              - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                            availability: in stock
                        next_cursor: AQHRcursorEXAMPLE
                    success: true
                cached:
                  summary: Same page from the cache
                  value:
                    code: 200
                    data:
                      success: true
                      cached: true
                      catalog:
                        id: ""
                        business_jid: 5511988888888@s.whatsapp.net
                        products:
                          - id: "7123456789012345"
                            retailer_id: CHAIR-001
                            name: Wooden chair
                            description: Solid wood chair with natural finish.
                            price: 3000000
                            currency: BRL
                            image_urls:
                              - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                            availability: in stock
                    success: true
        "400":
          description: 'Invalid request: `invalid JID "5511@@example"`; `limit must be between 1 and 100`; `limit must be provided once`; `cursor must be provided once and contain at most 2048 bytes`.'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: Invalid JID
                  value:
                    code: 400
                    error: invalid JID "5511@@example"
                    success: false
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 100
                    success: false
                repeated_limit:
                  summary: Repeated limit
                  value:
                    code: 400
                    error: limit must be provided once
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: "No session, client not connected, or the WhatsApp request failed: `no session`; `client not connected`; `failed to fetch catalog: upstream query rejected`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                graphql_rejected:
                  summary: GraphQL rejected
                  value:
                    code: 500
                    error: "failed to fetch catalog: upstream query rejected"
                    success: false
        "502":
          description: WhatsApp returned no catalog page at all. A product without a usable price never fails the page; it is returned with `price` null.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_page:
                  summary: Upstream returned no page
                  value:
                    code: 502
                    error: catalog upstream returned no page
                    success: false

  /business/products/{businessJID}:
    get:
      tags:
        - Business
      summary: List business products
      description: |
        Lists one page of products from the public catalog of a business (same GraphQL `QueryCatalog` read as the catalog route, without the cache).

        **Features:**
        - `products[]` in the compact shape (numeric `price` in thousandths, `null` when the product has no usable price)
        - `next_cursor` for the next page (`null` on the last page)

        **Rules:**
        - The session is checked first (`500 no session` / `client not connected`), then the path JID and pagination (`400`)
        - Hidden products and products under Meta review are not returned by this public read; use `GET /business/catalog/own` for the own catalog
        - GraphQL rejections return `500 failed to list products: upstream query rejected`; other failures return `500 failed to list products: <reason>`
        - A product without price (or with a non-numeric price) is returned with `price: null`; only a missing upstream page returns `502`
      parameters:
        - name: businessJID
          in: path
          required: true
          description: Business phone number or JID (`5511988888888`, `@s.whatsapp.net` or `@lid`).
          schema:
            type: string
          example: 5511988888888@s.whatsapp.net
        - name: limit
          in: query
          required: false
          description: Page size, 1-100 (default 20). Must appear at most once.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          example: 20
        - name: cursor
          in: query
          required: false
          description: Opaque cursor from `next_cursor` of the previous page. At most once and 2048 bytes.
          schema:
            type: string
            maxLength: 2048
          example: AQHRcursorEXAMPLE
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Products page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Product list result.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          products:
                            type: array
                            description: Products on this page.
                            items:
                              $ref: "#/components/schemas/BusinessLegacyProduct"
                          next_cursor:
                            type: string
                            description: Cursor for the next page; null on the last page.
                            nullable: true
                            example: AQHRcursorEXAMPLE
              examples:
                page:
                  summary: Page with one product
                  value:
                    code: 200
                    data:
                      success: true
                      products:
                        - id: "7123456789012345"
                          retailer_id: CHAIR-001
                          name: Wooden chair
                          description: Solid wood chair with natural finish.
                          price: 3000000
                          currency: BRL
                          image_urls:
                            - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                          availability: in stock
                      next_cursor: AQHRcursorEXAMPLE
                    success: true
                last_page:
                  summary: Last page
                  value:
                    code: 200
                    data:
                      success: true
                      products:
                        - id: "7123456789012345"
                          retailer_id: CHAIR-001
                          name: Wooden chair
                          description: Solid wood chair with natural finish.
                          price: 3000000
                          currency: BRL
                          image_urls:
                            - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                          availability: in stock
                      next_cursor: null
                    success: true
        "400":
          description: 'Invalid request: `invalid JID "5511@@example"`; `limit must be between 1 and 100`; `limit must be provided once`; `cursor must be provided once and contain at most 2048 bytes`.'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: Invalid JID
                  value:
                    code: 400
                    error: invalid JID "5511@@example"
                    success: false
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 100
                    success: false
                repeated_limit:
                  summary: Repeated limit
                  value:
                    code: 400
                    error: limit must be provided once
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: "No session, client not connected, or the WhatsApp request failed: `no session`; `client not connected`; `failed to list products: upstream query rejected`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                graphql_rejected:
                  summary: GraphQL rejected
                  value:
                    code: 500
                    error: "failed to list products: upstream query rejected"
                    success: false
        "502":
          description: WhatsApp returned no catalog page at all. A product without a usable price never fails the page; it is returned with `price` null.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_page:
                  summary: Upstream returned no page
                  value:
                    code: 502
                    error: catalog upstream returned no page
                    success: false

  /business/collections/{businessJID}:
    get:
      tags:
        - Business
      summary: List business product collections
      description: |
        Lists the public collections of a business (GraphQL `QueryProductCollections`) with the product IDs of each one.

        **Features:**
        - `collections[]` with `id`, `name`, `product_ids` (up to 100 per collection) and a per-collection `next_cursor`
        - Top-level `next_cursor` for the next page of collections

        **Rules:**
        - `limit` is the number of collections per page: 1-**20** (default 20)
        - The session is checked first (`500`), then the path JID and pagination (`400`)
        - Collections or products hidden or under review are not listed
        - GraphQL rejections return `500 failed to list collections: upstream query rejected`
      parameters:
        - name: businessJID
          in: path
          required: true
          description: Business phone number or JID (`5511988888888`, `@s.whatsapp.net` or `@lid`).
          schema:
            type: string
          example: 5511988888888@s.whatsapp.net
        - name: limit
          in: query
          required: false
          description: Page size, 1-20 (default 20). Must appear at most once.
          schema:
            type: integer
            minimum: 1
            maximum: 20
            default: 20
          example: 20
        - name: cursor
          in: query
          required: false
          description: Opaque cursor from `next_cursor` of the previous page. At most once and 2048 bytes.
          schema:
            type: string
            maxLength: 2048
          example: AQHRcursorEXAMPLE
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Collections page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Collections read result.
                        properties:
                          success:
                            type: boolean
                            description: Always true on success.
                            example: true
                          collections:
                            type: array
                            description: Collections on this page.
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  description: Collection ID.
                                  example: "7123456789012350"
                                name:
                                  type: string
                                  description: Collection name.
                                  example: Chairs
                                product_ids:
                                  type: array
                                  description: Product IDs in the collection (first page).
                                  items:
                                    type: string
                                  example:
                                    - "7123456789012345"
                                    - "7123456789012346"
                                next_cursor:
                                  type: string
                                  description: Cursor for more products of this collection, when present.
                                  example: AQHRcursorPRODUCTS
                          next_cursor:
                            type: string
                            description: Cursor for the next page of collections; null on the last page.
                            nullable: true
              examples:
                one_collection:
                  summary: One collection
                  value:
                    code: 200
                    data:
                      success: true
                      collections:
                        - id: "7123456789012350"
                          name: Chairs
                          product_ids:
                            - "7123456789012345"
                            - "7123456789012346"
                      next_cursor: null
                    success: true
        "400":
          description: 'Invalid request: `invalid JID "5511@@example"`; `limit must be between 1 and 20`; `limit must be provided once`; `cursor must be provided once and contain at most 2048 bytes`.'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_jid:
                  summary: Invalid JID
                  value:
                    code: 400
                    error: invalid JID "5511@@example"
                    success: false
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 20
                    success: false
                repeated_limit:
                  summary: Repeated limit
                  value:
                    code: 400
                    error: limit must be provided once
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: "No session, client not connected, or the WhatsApp request failed: `no session`; `client not connected`; `failed to list collections: upstream query rejected`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                graphql_rejected:
                  summary: GraphQL rejected
                  value:
                    code: 500
                    error: "failed to list collections: upstream query rejected"
                    success: false
        "502":
          description: WhatsApp returned no page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_upstream_page:
                  summary: Empty upstream page
                  value:
                    code: 502
                    error: collections upstream returned no page
                    success: false

  /business/catalog/send:
    post:
      tags:
        - Business
      summary: Send a catalog link message
      description: |
        Sends a plain text message with the catalog link (`https://wa.me/c/<phone>`) of a business.

        **Features:**
        - Works for any recipient chat (user, LID or group)
        - Optional `caption` placed before the link

        **Rules:**
        - `to` and `business_jid` are required (`400 to and business_jid are required`)
        - `business_jid` may be a phone number, a non-device `@s.whatsapp.net` JID or a `@lid` with a stored phone mapping; it becomes `https://wa.me/c/<phone>`
        - When no phone link can be derived (a `@lid` without a known phone mapping, group or device JIDs, or any other value) the original text is kept: `Catálogo: <business_jid as sent>`, never a `400`
        - The sent text is `<caption>` + blank line + `Catálogo: <link>`; the `Catálogo:` prefix is a fixed Portuguese label written by the gateway
        - Send failures return `500 failed to send catalog: <reason>`
      requestBody:
        required: true
        description: Recipient, catalog owner and optional caption.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BusinessCatalogSendRequest"
            examples:
              with_caption:
                summary: Catalog link with caption
                description: Sends the caption, a blank line and the catalog link.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  caption: Check out our full catalog
              link_only:
                summary: Catalog link only
                description: Sends only the labeled catalog link.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: "5511988888888"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Catalog link sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Send acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `to and business_jid are required`; `invalid to JID`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                required_fields_missing:
                  summary: Required fields missing
                  value:
                    code: 400
                    error: to and business_jid are required
                    success: false
                invalid_recipient:
                  summary: Invalid recipient
                  value:
                    code: 400
                    error: invalid to JID
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, client not connected, or the send failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send catalog: server returned error 479"
                    success: false

  /business/product/send:
    post:
      tags:
        - Business
      summary: Send a product card
      description: |
        Sends a single product card (`ProductMessage`) that opens a catalog product.

        **Modes:**
        - **ID only** (no `snapshot`, no `use_catalog`): card with the product ID and `caption` as description; `snapshot_complete: false`
        - **`snapshot`**: explicit card fields (title, description, currency, `price_amount1000`, retailer ID, URL and optional image)
        - **`use_catalog: true`**: loads the real product from the owner's catalog (image downloaded only from `*.whatsapp.net` / `*.fbcdn.net`); `snapshot` fields override single values

        **Rules:**
        - `to`, `business_jid` and `product_id` are required (`400 to, business_jid and product_id are required`; `invalid to JID`; `invalid business_jid`)
        - When a phone/LID mapping is known the owner's LID is used; the given owner is never replaced by the session owner
        - Invalid snapshots return `400 invalid snapshot` or the product builder error; invalid images return `400` (base64, size, JPEG/PNG, 40 megapixels)
        - Catalog failures return `502` (hidden or under-review products are not readable publicly); image uploads failing return `500 failed to upload product image`
        - `quoted_msg_id` applies only to a real outgoing message stored for this chat; otherwise the card is sent unquoted (`quoted_message_reason`: `not_requested`, `applied`, `not_available_in_session_chat`, or `lookup_failed` when the stored history could not be read)
        - With `use_catalog`, a product without a usable integer price is sent without price instead of failing
        - The body is limited to 24 MiB

        **Response:**
        - `product_image_applied` reports protocol inclusion, not confirmation that the recipient rendered the image
      requestBody:
        required: true
        description: Recipient, catalog owner, product and card mode.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BusinessProductSendRequest"
            examples:
              id_only:
                summary: ID-only product card
                description: Minimal card that references the product ID.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  product_id: "7123456789012345"
                  caption: Our best seller
              snapshot:
                summary: Explicit snapshot
                description: Card with explicit title, price, retailer ID and URL.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  product_id: "7123456789012345"
                  caption: Our best seller
                  snapshot:
                    title: Wooden chair
                    description: Solid wood chair with natural finish.
                    currency: BRL
                    price_amount1000: 3000000
                    retailer_id: CHAIR-001
                    url: https://example.com/products/chair
              use_catalog:
                summary: Catalog data with a title override and quote
                description: Loads the product from the catalog, overrides only the title and quotes an earlier outgoing message.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  product_id: "7123456789012345"
                  use_catalog: true
                  snapshot:
                    title: Wooden chair - 20% off
                  quoted_msg_id: 3EB0C767D26A1D6B8A9F
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Product card sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Send acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          snapshot_complete:
                            type: boolean
                            description: True when the card was built from `snapshot` or the catalog.
                            example: true
                          product_image_applied:
                            type: boolean
                            description: True when a product image was included in the message.
                            example: true
                          quoted_message_applied:
                            type: boolean
                            description: True when the message quotes `quoted_msg_id`.
                            example: false
                          quoted_message_reason:
                            type: string
                            description: Quote outcome.
                            enum:
                              - not_requested
                              - applied
                              - not_available_in_session_chat
                            example: not_requested
              examples:
                id_only:
                  summary: ID-only card
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      snapshot_complete: false
                      product_image_applied: false
                      quoted_message_applied: false
                      quoted_message_reason: not_requested
                    success: true
                catalog_card:
                  summary: Catalog card with image and quote
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      snapshot_complete: true
                      product_image_applied: true
                      quoted_message_applied: true
                      quoted_message_reason: applied
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `to, business_jid and product_id are required`; `invalid snapshot`; `invalid product image base64 or size`; `product image must be JPEG or PNG`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                required_fields_missing:
                  summary: Required fields missing
                  value:
                    code: 400
                    error: to, business_jid and product_id are required
                    success: false
                invalid_snapshot:
                  summary: Invalid snapshot
                  value:
                    code: 400
                    error: invalid snapshot
                    success: false
                image_too_large:
                  summary: Image too large
                  value:
                    code: 400
                    error: invalid product image base64 or size
                    success: false
                image_not_jpeg_or_png:
                  summary: Image not JPEG or PNG
                  value:
                    code: 400
                    error: product image must be JPEG or PNG
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, client not connected, media upload, quote lookup or send failure.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                upload_failed:
                  summary: Upload failed
                  value:
                    code: 500
                    error: failed to upload product image
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send product: server returned error 479"
                    success: false
        "502":
          description: The catalog product or its image could not be loaded (`use_catalog`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                product_not_readable:
                  summary: Product not readable
                  value:
                    code: 502
                    error: catalog product unavailable
                    success: false
                image_host_not_allowed:
                  summary: Image host not allowed
                  value:
                    code: 502
                    error: catalog image host is not allowed
                    success: false

  /business/product-list/send:
    post:
      tags:
        - Business
      summary: Send a product list message
      description: |
        Sends a product list (`ListMessage` of type product list) with one or more sections of catalog products.

        **Features:**
        - 1-10 sections, up to 30 products in total
        - Optional header thumbnail from a catalog product (`use_catalog: true`, `header_product_id`)
        - The list button text is the fixed Portuguese label `Ver produtos`; an empty `header` becomes `Catálogo`

        **Rules:**
        - `to`, `business_jid` and `sections` are required (`400 to, business_jid and sections are required`)
        - Lists outside the official builder limits (more than 10 sections or 30 products, duplicate IDs, long texts) are still sent in the original unvalidated format, as before; recipients' apps may not render them
        - With `use_catalog`, `header_product_id` must belong to the sections (`400 header_product_id must belong to sections`); catalog or image failures return `502`
        - Send failures return `500 failed to send product list: <reason>`
      requestBody:
        required: true
        description: Recipient, catalog owner, texts and sections.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BusinessProductListSendRequest"
            examples:
              two_sections:
                summary: Product list with two sections
                description: Header, body, footer and two sections of products.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  header: Weekly offers
                  body: Select the items you want
                  footer: Valid until Sunday
                  sections:
                    - title: Chairs
                      products:
                        - product_id: "7123456789012345"
                        - product_id: "7123456789012346"
                    - title: Tables
                      products:
                        - product_id: "7123456789012347"
              catalog_header:
                summary: Header thumbnail from the catalog
                description: Uses the image of `header_product_id` as the header thumbnail.
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  use_catalog: true
                  header_product_id: "7123456789012346"
                  body: Select the items you want
                  sections:
                    - title: Chairs
                      products:
                        - product_id: "7123456789012345"
                        - product_id: "7123456789012346"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Product list sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Send acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          product_image_applied:
                            type: boolean
                            description: True when a header thumbnail was included.
                            example: false
                          sections:
                            type: integer
                            description: Number of sections sent.
                            example: 2
              examples:
                two_sections:
                  summary: Two sections acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      product_image_applied: false
                      sections: 2
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `to, business_jid and sections are required`; `invalid to JID`; `invalid business_jid`; with `use_catalog`, `header_product_id must belong to sections`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                required_fields_missing:
                  summary: Required fields missing
                  value:
                    code: 400
                    error: to, business_jid and sections are required
                    success: false
                header_product_outside_sections:
                  summary: Header product outside sections
                  value:
                    code: 400
                    error: header_product_id must belong to sections
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: No session, client not connected, or the send failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send product list: server returned error 479"
                    success: false
        "502":
          description: The header product or its image could not be loaded (`use_catalog`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                header_product_not_readable:
                  summary: Header product not readable
                  value:
                    code: 502
                    error: catalog header product unavailable
                    success: false
                header_product_without_image:
                  summary: Header product without image
                  value:
                    code: 502
                    error: catalog header product image unavailable
                    success: false
                invalid_header_image:
                  summary: Invalid header image
                  value:
                    code: 502
                    error: invalid catalog header image
                    success: false

  /business/shop/send:
    post:
      tags:
        - Business
      summary: Send a shop link message
      description: |
        Sends a plain text message with the shop link (`https://wa.me/c/<phone>`) of a business. Requires an Enterprise license.

        **Features:**
        - Text is `<caption> <link>`
        - Without `caption` the gateway writes the fixed Portuguese label `Acesse nossa loja:` before the link

        **Rules:**
        - The Enterprise license is checked first (`403`), then the session (`500`), then the body (`400`)
        - `to` and `business_jid` are required (`400 to and business_jid are required`)
        - `business_jid` may be a phone number, a non-device `@s.whatsapp.net` JID or a `@lid` with a stored phone mapping; it becomes `https://wa.me/c/<phone>`
        - When no phone link can be derived (a `@lid` without a known phone mapping, group or device JIDs, or any other value) the original text is kept: `<caption> wa.me/<business_jid as sent>`, never a `400`
        - Send failures return `500 failed to send shop: <reason>`
      requestBody:
        required: true
        description: Recipient, shop owner and optional caption.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BusinessShopSendRequest"
            examples:
              shop_link:
                summary: Shop link with caption
                description: "Sends `Visit our official shop: https://wa.me/c/5511988888888`."
                value:
                  to: 5511999999999@s.whatsapp.net
                  business_jid: 5511988888888@s.whatsapp.net
                  caption: "Visit our official shop:"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Shop link sent
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Send acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
              examples:
                sent:
                  summary: Message acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                    success: true
        "400":
          description: "Invalid request: `could not decode payload`; `to and business_jid are required`; `invalid to JID`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                body_is_not_json:
                  summary: Body is not JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                required_fields_missing:
                  summary: Required fields missing
                  value:
                    code: 400
                    error: to and business_jid are required
                    success: false
                invalid_recipient:
                  summary: Invalid recipient
                  value:
                    code: 400
                    error: invalid to JID
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "403":
          description: Enterprise license required. The gateway returns this fixed message in Portuguese.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enterprise_license_missing:
                  summary: Enterprise license missing
                  value:
                    code: 403
                    error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                    success: false
        "500":
          description: No session, client not connected, or the send failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                send_failed:
                  summary: Send failed
                  value:
                    code: 500
                    error: "failed to send shop: server returned error 479"
                    success: false

  /device/users:
    post:
      tags:
        - Device
      summary: List the devices of WhatsApp accounts
      description: |
        Lists the devices (phone and companion devices such as WhatsApp Web or Desktop) registered to one or more WhatsApp accounts, using the library device cache or a WhatsApp usync query.

        **Rules:**
        - `user_jids` is required and must be a non-empty array. Each item may be a phone number with country code, a phone JID or a LID; the first invalid item aborts with **400**.
        - The session's own device is never included, even when your own JID is queried.
        - A missing WhatsApp session returns **500** `no session`; a failed query returns **500**.

        **Response:**
        - One flat `devices` list for all requested accounts, with `count` items.
        - `platform` is derived from the device slot number (`device_id`) with the same table as `POST /device/platform`; it is not the real client platform. Slot `0` is the primary phone and shows `UNKNOWN`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserDevicesRequest"
            examples:
              two_users:
                summary: Devices of two accounts
                description: Mixes a phone JID and a bare phone number.
                value:
                  user_jids:
                    - 5511999999999@s.whatsapp.net
                    - "5511988888888"
              lid_user:
                summary: Devices of an account addressed by LID
                description: LIDs are accepted; returned device JIDs keep the LID server.
                value:
                  user_jids:
                    - 123456789012345@lid
      responses:
        "200":
          description: Devices of the requested accounts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceUsersResponse"
              example:
                code: 200
                data:
                  success: true
                  devices:
                    - user_jid: 5511999999999@s.whatsapp.net
                      device_jid: 5511999999999@s.whatsapp.net
                      device_id: 0
                      platform: UNKNOWN
                    - user_jid: 5511999999999@s.whatsapp.net
                      device_jid: 5511999999999:7@s.whatsapp.net
                      device_id: 7
                      platform: DESKTOP
                    - user_jid: 5511988888888@s.whatsapp.net
                      device_jid: 5511988888888@s.whatsapp.net
                      device_id: 0
                      platform: UNKNOWN
                  count: 3
                success: true
        "400":
          description: "Bad Request: invalid JSON, `user_jids` missing or empty, or an item is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_user_jids:
                  summary: user_jids missing or empty
                  value:
                    code: 400
                    error: missing user_jids in payload
                    success: false
                invalid_jid:
                  summary: An item cannot be parsed
                  value:
                    code: 400
                    error: "invalid JID: @s.whatsapp.net"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the device query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: Device query failed
                  value:
                    code: 500
                    error: failed to get devices
                    success: false

  /device/users/context:
    post:
      tags:
        - Device
      summary: List account devices with a custom timeout
      description: |
        Lists the devices of one or more WhatsApp accounts like `POST /device/users`, but runs the query under a caller-defined timeout and echoes a free-text `context` tag back in the response.

        **Rules:**
        - `user_jids` is required and must be a non-empty array of phone numbers, phone JIDs or LIDs.
        - `timeout` is a Go duration in **nanoseconds** (`10000000000` = 10 s). Omitted or `0` means 30 s. Small numbers such as `10` mean 10 ns and make the query fail at once.
        - The timeout context is not tied to the HTTP request; a timeout returns **500** `failed to get devices` (there is no 408).
        - The session's own device is never included.

        **Response:**
        - Same `devices` and `count` as `POST /device/users`, plus `context` (empty string when not sent).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeviceUsersContextRequest"
            examples:
              with_timeout:
                summary: 10-second timeout with a tag
                description: The tag is echoed in `data.context` and written to the gateway logs.
                value:
                  user_jids:
                    - 5511999999999@s.whatsapp.net
                    - "5511977777777"
                  context: crm-sync
                  timeout: 10000000000
              default_timeout:
                summary: Default 30-second timeout
                description: Without `timeout` the query may take up to 30 seconds.
                value:
                  user_jids:
                    - 123456789012345@lid
      responses:
        "200":
          description: Devices of the requested accounts and the echoed tag.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceUsersContextResponse"
              example:
                code: 200
                data:
                  success: true
                  devices:
                    - user_jid: 5511999999999@s.whatsapp.net
                      device_jid: 5511999999999@s.whatsapp.net
                      device_id: 0
                      platform: UNKNOWN
                    - user_jid: 5511999999999@s.whatsapp.net
                      device_jid: 5511999999999:7@s.whatsapp.net
                      device_id: 7
                      platform: DESKTOP
                  count: 2
                  context: crm-sync
                success: true
        "400":
          description: "Bad Request: invalid JSON (including a non-integer `timeout`), `user_jids` missing or empty, or an item is not a valid number/JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON or timeout is not an integer
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_user_jids:
                  summary: user_jids missing or empty
                  value:
                    code: 400
                    error: missing user_jids in payload
                    success: false
                invalid_jid:
                  summary: An item cannot be parsed
                  value:
                    code: 400
                    error: "invalid JID: @s.whatsapp.net"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the device query failed or timed out."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: Query failed or exceeded the timeout
                  value:
                    code: 500
                    error: failed to get devices
                    success: false

  /device/linked:
    get:
      tags:
        - Device
      summary: List devices linked to your account
      description: |
        Lists the devices registered to the connected account: the primary phone and the other companion devices (WhatsApp Web, Desktop, other gateways).

        **Rules:**
        - No query parameters or body. The session must be paired; before pairing the call returns **500** `not logged in`.
        - The library never returns the session's own device, so this gateway is not in the list and `is_current` is normally `false` for every entry.

        **Response:**
        - `jid` is the account JID; `linked_devices` has `count` items.
        - `platform` is derived from the device slot number with the table of `POST /device/platform`, not detected from the real client.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Devices registered to the account.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkedDevicesResponse"
              example:
                code: 200
                data:
                  success: true
                  jid: 5511999999999:12@s.whatsapp.net
                  linked_devices:
                    - device_jid: 5511999999999@s.whatsapp.net
                      device_id: 0
                      platform: UNKNOWN
                      is_current: false
                    - device_jid: 5511999999999:7@s.whatsapp.net
                      device_id: 7
                      platform: DESKTOP
                      is_current: false
                  count: 2
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, session not paired, or the device query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_logged_in:
                  summary: Session not paired yet
                  value:
                    code: 500
                    error: not logged in
                    success: false
                query_failed:
                  summary: Device query failed
                  value:
                    code: 500
                    error: failed to get linked devices
                    success: false

  /device/platform:
    post:
      tags:
        - Device
      summary: Translate a device ID to a platform name
      description: |
        Returns the platform label this gateway associates with a numeric device ID. It is a local lookup table: no WhatsApp session or network call is involved.

        **Rules:**
        - `device_id` is optional; a missing field is treated as `0`.
        - Known IDs: 0 `UNKNOWN`, 1 `CHROME`, 2 `FIREFOX`, 3 `IE`, 4 `OPERA`, 5 `SAFARI`, 6 `EDGE`, 7 `DESKTOP`, 8 `IPAD`, 9 `ANDROID_TABLET`, 10 `OHANA`, 11 `ALOHA`, 12 `CATALINA`, 13 `TCL_TV`. Any other number returns `UNKNOWN`.
        - A non-integer `device_id` fails to decode and returns **400**.

        **Use Cases:**
        - Label the `device_id` values returned by `POST /device/users` and `GET /device/linked`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DevicePlatformRequest"
            examples:
              desktop:
                summary: Known ID
                description: Device ID 7 maps to `DESKTOP`.
                value:
                  device_id: 7
              unknown:
                summary: ID outside the table
                description: Unmapped IDs return `UNKNOWN`.
                value:
                  device_id: 42
      responses:
        "200":
          description: Platform label for the device ID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevicePlatformResponse"
              example:
                code: 200
                data:
                  success: true
                  device_id: 7
                  platform: DESKTOP
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON or `device_id` is not an integer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 400
                error: could not decode payload
                success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: the JSON response could not be serialized. Not expected in practice, because the payload only holds an integer and strings."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: "json: error calling MarshalJSON"
                success: false

  /privacy/settings:
    get:
      tags:
        - Privacy
      summary: Get privacy settings
      description: |
        Returns the privacy settings of the connected account grouped under `privacy_settings`, using the settings cached by the WhatsApp library (fetched from WhatsApp on first use).

        **Rules:**
        - No query parameters or body.
        - If WhatsApp cannot be queried, the call still returns **200** with empty strings. Use `GET /user/privacy/settings` when a failed fetch must surface as an error.

        **Values:**
        - `group_add`, `last_seen`, `status`, `profile`: `all`, `contacts`, `contact_blacklist` or `none`.
        - `read_receipts`: `all` or `none`. `online`: `all` or `match_last_seen`. `call_add`: `all` or `known`.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Cached privacy settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrivacySettingsResponse"
              examples:
                fetched:
                  summary: Settings available
                  value:
                    code: 200
                    data:
                      success: true
                      privacy_settings:
                        group_add: contacts
                        last_seen: contacts
                        status: contacts
                        profile: all
                        read_receipts: all
                        online: match_last_seen
                        call_add: all
                    success: true
                fetch_failed:
                  summary: WhatsApp could not be queried
                  value:
                    code: 200
                    data:
                      success: true
                      privacy_settings:
                        group_add: ""
                        last_seen: ""
                        status: ""
                        profile: ""
                        read_receipts: ""
                        online: ""
                        call_add: ""
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false
    post:
      tags:
        - Privacy
      summary: Change a privacy setting
      description: |
        Changes one privacy setting of the connected account, such as who can see your last seen or add you to groups.

        **Rules:**
        - `setting` and `value` are required; both are case-insensitive.
        - `setting`: `group_add`, `last_seen`, `status`, `profile`, `read_receipts`, `online` or `call_add`.
        - `value`: `all`, `contacts`, `contact_blacklist` or `none`. The gateway does not accept `match_last_seen` or `known`, so `online` and `call_add` can only be set to `all` here.
        - WhatsApp rejects combinations it does not support (for example `read_receipts` with `contacts`) with **500**.
        - `users` is validated (an invalid JID returns **400**) but then ignored: per-contact exception lists are not applied by this endpoint.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetPrivacySettingRequest"
            examples:
              last_seen_contacts:
                summary: Last seen visible to contacts only
                description: Hides the last seen time from people who are not in your contacts.
                value:
                  setting: last_seen
                  value: contacts
              groups_contacts:
                summary: Only contacts can add you to groups
                description: People outside your contacts must send an invite instead.
                value:
                  setting: group_add
                  value: contacts
              read_receipts_off:
                summary: Turn off read receipts
                description: Stops sending blue ticks; you also stop seeing them from others.
                value:
                  setting: read_receipts
                  value: none
      responses:
        "200":
          description: Setting changed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrivacySetSettingResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Privacy setting last_seen updated to contacts
                  setting: last_seen
                  value: contacts
                success: true
        "400":
          description: "Bad Request: invalid JSON, `setting`/`value` missing or not allowed, or an invalid JID in `users`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: setting or value missing
                  value:
                    code: 400
                    error: missing setting or value in payload
                    success: false
                invalid_setting:
                  summary: setting not in the allowed list
                  value:
                    code: 400
                    error: invalid setting type
                    success: false
                invalid_value:
                  summary: value not in the allowed list
                  value:
                    code: 400
                    error: invalid privacy value
                    success: false
                invalid_user:
                  summary: An item of users cannot be parsed
                  value:
                    code: 400
                    error: "invalid JID: @s.whatsapp.net"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp rejected the change."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: WhatsApp rejected the change
                  value:
                    code: 500
                    error: failed to update privacy setting
                    success: false

  /privacy/disappearing-timer:
    post:
      tags:
        - Privacy
      summary: Set the default disappearing-message timer
      description: |
        Sets the default disappearing-message timer applied to new one-to-one chats of the connected account. Existing chats keep their own timer.

        **Rules:**
        - `timer` is required and case-insensitive: `24h`, `7d`, `90d`, or `off` (also `0`) to disable.
        - Any other value returns **400**; custom durations are not supported.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DisappearingTimerRequest"
            examples:
              seven_days:
                summary: Messages disappear after 7 days
                description: New chats start with a 7-day timer.
                value:
                  timer: 7d
              one_day:
                summary: Messages disappear after 24 hours
                description: The shortest timer offered by WhatsApp.
                value:
                  timer: 24h
              disable:
                summary: Turn the default timer off
                description: New chats start without disappearing messages.
                value:
                  timer: "off"
      responses:
        "200":
          description: Default timer updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrivacyDisappearingTimerResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Default disappearing timer set to 7d
                  timer: 7d
                success: true
        "400":
          description: "Bad Request: invalid JSON, `timer` missing, or not one of the allowed values."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_timer:
                  summary: timer missing or empty
                  value:
                    code: 400
                    error: missing timer in payload
                    success: false
                invalid_timer:
                  summary: timer not allowed
                  value:
                    code: 400
                    error: "invalid timer value. Use: 24h, 7d, 90d, or off"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp rejected the timer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                update_failed:
                  summary: WhatsApp rejected the timer
                  value:
                    code: 500
                    error: failed to set disappearing timer
                    success: false

  /privacy/blocklist:
    get:
      tags:
        - Privacy
      summary: List blocked contacts
      description: |
        Returns the JIDs blocked by the connected account, fetched from WhatsApp.

        **Rules:**
        - No query parameters or body; the list is always fetched from WhatsApp.
        - A failed WhatsApp query returns **500** `failed to get blocklist`.

        **Response:**
        - `blocked_contacts` is `null` (not an empty array) when nobody is blocked; `count` is then `0`.
        - Entries can be phone JIDs or LIDs, as reported by WhatsApp.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Blocked JIDs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlocklistResponse"
              examples:
                with_blocked:
                  summary: Two blocked contacts
                  value:
                    code: 200
                    data:
                      success: true
                      blocked_contacts:
                        - 5511977777777@s.whatsapp.net
                        - 123456789012347@lid
                      count: 2
                    success: true
                empty:
                  summary: Nobody blocked
                  value:
                    code: 200
                    data:
                      success: true
                      blocked_contacts: null
                      count: 0
                    success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the blocklist query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                query_failed:
                  summary: Blocklist query failed
                  value:
                    code: 500
                    error: failed to get blocklist
                    success: false
    post:
      tags:
        - Privacy
      summary: Block or unblock contacts
      description: |
        Blocks or unblocks one or more contacts of the connected account. Each contact is sent to WhatsApp as a separate request.

        **Rules:**
        - `action` (`block` or `unblock`, case-insensitive) and a non-empty `users` array are required.
        - Each item of `users` may be a phone number with country code or a JID; the first invalid item aborts with **400** before anything is sent.
        - The request fails with **500** only when every contact failed. Partial failures still return **200**, and `message` counts all requested contacts, not only the successful ones.

        **Response:**
        - `message` is built from `action` as sent (for example `Successfully blocked 2 contact(s)`); `action` and `users` echo the request.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateBlocklistRequest"
            examples:
              block:
                summary: Block two contacts
                description: Accepts phone numbers and JIDs in the same list.
                value:
                  action: block
                  users:
                    - "5511977777777"
                    - 5511966666666@s.whatsapp.net
              unblock:
                summary: Unblock one contact
                description: Removes the contact from the blocklist.
                value:
                  action: unblock
                  users:
                    - "5511977777777"
      responses:
        "200":
          description: At least one contact was updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrivacyBlocklistUpdateResponse"
              example:
                code: 200
                data:
                  success: true
                  message: Successfully blocked 2 contact(s)
                  action: block
                  users:
                    - "5511977777777"
                    - 5511966666666@s.whatsapp.net
                success: true
        "400":
          description: "Bad Request: invalid JSON, `action` or `users` missing, unknown action, or an invalid item in `users`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode payload
                    success: false
                missing_fields:
                  summary: action or users missing
                  value:
                    code: 400
                    error: missing action or users in payload
                    success: false
                invalid_action:
                  summary: action is neither block nor unblock
                  value:
                    code: 400
                    error: "invalid action. Use: block or unblock"
                    success: false
                invalid_user:
                  summary: An item of users cannot be parsed
                  value:
                    code: 400
                    error: "invalid JID: @s.whatsapp.net"
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or WhatsApp rejected every contact in the list."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                all_failed:
                  summary: Every contact failed
                  value:
                    code: 500
                    error: failed to update blocklist
                    success: false

  /privacy/status:
    get:
      tags:
        - Privacy
      summary: Get the status-privacy setting
      description: |
        Returns only the "status" privacy setting of the connected account: who can see your status updates. Reads the same cached settings as `GET /privacy/settings`.

        **Rules:**
        - No query parameters or body.
        - When WhatsApp cannot be queried, `status_privacy` is an empty string and the response is still **200**.

        **Response:**
        - `status_privacy`: `all`, `contacts`, `contact_blacklist` or `none`.
        - `description` is a fixed explanatory string.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Status privacy setting.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusPrivacyResponse"
              example:
                code: 200
                data:
                  success: true
                  status_privacy: contacts
                  description: Status privacy setting (all, contacts, contact_blacklist, or none)
                success: true
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 500
                error: no session
                success: false
  /business/cart:
    put:
      tags:
        - Business
      summary: Enable or disable the shopping cart
      description: |
        Turns the catalog shopping cart ("Add to cart") on or off for the connected business account.

        **Rules:**
        - `enabled` is required (`400 enabled is required`)
        - WhatsApp must echo the new `cart_enabled` value; a missing or different value returns `502`
        - On success `data.data` is `null` and this user's catalog cache is cleared

        **Use Cases:**
        - Disable the cart for catalogs used only for browsing
        - Re-enable ordering after a stock break
      requestBody:
        required: true
        description: Target cart state.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementEnabledRequest"
            examples:
              enable:
                summary: Enable cart
                description: Buyers can add products to a cart and send an order.
                value:
                  enabled: true
              disable:
                summary: Disable cart
                description: The catalog stays visible but the cart is turned off.
                value:
                  enabled: false
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Cart setting updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                updated:
                  summary: Cart setting applied
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `enabled is required`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enabled_missing:
                  summary: enabled missing
                  value:
                    code: 400
                    error: enabled is required
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                server_did_not_echo_the_value:
                  summary: Server did not echo the value
                  value:
                    code: 502
                    error: "business operation failed: update business cart setting: business commerce settings response is missing cart_enabled"
                    success: false
  /business/catalog:
    post:
      tags:
        - Business
      summary: Create the business catalog
      description: |
        Creates the product catalog of the connected business account (Meta mutation `xfb_whatsapp_catalog_create`).

        **Use Cases:**
        - Run once on accounts that have no catalog yet, before `POST /business/products`

        **Rules:**
        - The body may be omitted or be an empty object `{}`; any property returns `400 invalid JSON body (maximum 24 MiB)`
        - If the server response has no `product_catalog` the call fails with `502`
        - On success `data.data` is `null` and this user's catalog cache is cleared
      requestBody:
        required: false
        description: Empty object `{}`; the body may also be omitted.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BusinessEmptyRequest"
            examples:
              empty:
                summary: Empty object
                description: No properties are accepted; sending `{}` is equivalent to omitting the body.
                value: {}
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Catalog created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                created:
                  summary: Catalog created
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                response_without_catalog:
                  summary: Response without catalog
                  value:
                    code: 502
                    error: "business operation failed: business catalog response is missing product_catalog"
                    success: false
  /business/catalog/{businessJID}/products/query:
    post:
      tags:
        - Business
      summary: Get several catalog products by ID
      description: |
        Fetches up to 100 products from the public catalog of `businessJID` in one call (GraphQL `QueryProductListCatalog`).

        **Features:**
        - `data.data` is an array in the same order as `product_ids`
        - Each item has the full product shape (prices, media, sale price, review status)

        **Rules:**
        - `product_ids` is required: 1-100 unique IDs of 1-256 bytes (`400 provide 1-100 IDs`, `duplicate ID`, `ID must contain 1-256 bytes`)
        - `businessJID` must be `@s.whatsapp.net` or `@lid`
        - If any requested product is missing (hidden, under review or deleted) the whole call fails with `502`
        - Read-only: the catalog cache is not touched
      parameters:
        - name: businessJID
          in: path
          required: true
          description: "Business owner JID: a phone JID (`@s.whatsapp.net`) or LID (`@lid`). Other servers return `400 businessJID must be a user JID`."
          schema:
            type: string
          example: 5511988888888@s.whatsapp.net
      requestBody:
        required: true
        description: Product IDs to fetch (1-100 unique).
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementIDsRequest"
            examples:
              single:
                summary: One product
                description: Fetch a single product by ID.
                value:
                  product_ids:
                    - "7123456789012345"
              several:
                summary: Several products
                description: Fetch two products; the response keeps this order.
                value:
                  product_ids:
                    - "7123456789012345"
                    - "7123456789012346"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Products in request order
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: array
                            description: Products in the order of `product_ids`.
                            items:
                              $ref: "#/components/schemas/BusinessCatalogProduct"
              examples:
                one_product:
                  summary: One product
                  value:
                    code: 200
                    data:
                      data:
                        - id: "7123456789012345"
                          retailer_id: CHAIR-001
                          name: Wooden chair
                          description: Solid wood chair with natural finish.
                          price: "3000000"
                          currency: BRL
                          url: https://example.com/products/chair/
                          is_hidden: false
                          is_sanctioned: false
                          product_availability: in stock
                          compliance_info:
                            country_code_origin: BR
                          media:
                            images:
                              - id: "7123456789012346"
                                original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                          sale_price:
                            price: "2499900"
                          status_info:
                            status: APPROVED
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `provide 1-100 IDs`; `duplicate ID`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`; `ID must contain 1-256 bytes`; `businessJID must be a user JID`; `limit must be between 1 and 100`; `cursor must be provided once and contain at most 2048 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_more_than_100_ids:
                  summary: Empty or more than 100 IDs
                  value:
                    code: 400
                    error: provide 1-100 IDs
                    success: false
                repeated_id:
                  summary: Repeated ID
                  value:
                    code: 400
                    error: duplicate ID
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
                businessjid_is_not_a_user_jid:
                  summary: businessJID is not a user JID
                  value:
                    code: 400
                    error: businessJID must be a user JID
                    success: false
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 100
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                product_missing_from_response:
                  summary: Product missing from response
                  value:
                    code: 502
                    error: 'business operation failed: product list response is missing requested product "7123456789012346"'
                    success: false
  /business/catalog/{businessJID}/products/{productID}:
    get:
      tags:
        - Business
      summary: Get one catalog product
      description: |
        Fetches one product from the public catalog of `businessJID` (GraphQL `QueryCatalogProduct`, including compliance information).

        **Rules:**
        - `businessJID` must be `@s.whatsapp.net` or `@lid`; `productID` 1-256 bytes
        - `limit` and `cursor` are validated like in paginated reads but not used here
        - Prices are strings in thousandths of the currency
        - Read-only: the catalog cache is not touched

        **Visibility:**
        - Hidden products and products still under Meta review are not exposed by public reads: the call returns `502` with GraphQL code `2498052`
        - Use `GET /business/catalog/own` to read your own hidden or pending products
      parameters:
        - name: businessJID
          in: path
          required: true
          description: "Business owner JID: a phone JID (`@s.whatsapp.net`) or LID (`@lid`). Other servers return `400 businessJID must be a user JID`."
          schema:
            type: string
          example: 5511988888888@s.whatsapp.net
        - name: productID
          in: path
          required: true
          description: Catalog product ID, 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012345"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Catalog product
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: Requested product.
                            allOf:
                              - $ref: "#/components/schemas/BusinessCatalogProduct"
              examples:
                approved:
                  summary: Approved product
                  value:
                    code: 200
                    data:
                      data:
                        id: "7123456789012345"
                        retailer_id: CHAIR-001
                        name: Wooden chair
                        description: Solid wood chair with natural finish.
                        price: "3000000"
                        currency: BRL
                        url: https://example.com/products/chair/
                        is_hidden: false
                        is_sanctioned: false
                        product_availability: in stock
                        compliance_info:
                          country_code_origin: BR
                        media:
                          images:
                            - id: "7123456789012346"
                              original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                        sale_price:
                          price: "2499900"
                        status_info:
                          status: APPROVED
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `ID must contain 1-256 bytes`; `businessJID must be a user JID`; `limit must be between 1 and 100`; `cursor must be provided once and contain at most 2048 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
                businessjid_is_not_a_user_jid:
                  summary: businessJID is not a user JID
                  value:
                    code: 400
                    error: businessJID must be a user JID
                    success: false
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 100
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                hidden_or_under_review:
                  summary: Hidden or under review
                  value:
                    code: 502
                    error: "business operation failed: QueryCatalogProduct: 2498052  (ERROR)"
                    success: false
  /business/collections:
    post:
      tags:
        - Business
      summary: Create a catalog collection
      description: |
        Creates a collection in the own catalog and returns its `id` and `review_status` (collections go through Meta review; observed value `STATUS_APPROVED`).

        **Rules:**
        - `name` is required, 1-256 bytes after trimming (`400 name is required (1-256 bytes)`)
        - `product_ids`: unique own product IDs of 1-256 bytes. The gateway accepts 0-100, but WhatsApp requires **1-100** and an empty list returns `502`
        - Products hidden or under review are not listed in public reads of the collection
        - On success this user's catalog cache is cleared
      requestBody:
        required: true
        description: Collection name and its products.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementCollectionRequest"
            examples:
              minimal:
                summary: One product
                description: Creates a collection with a single product.
                value:
                  name: Chairs
                  product_ids:
                    - "7123456789012345"
              several:
                summary: Several products
                description: Creates a collection with three products.
                value:
                  name: Living room
                  product_ids:
                    - "7123456789012345"
                    - "7123456789012346"
                    - "7123456789012347"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Collection created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: Created collection.
                            allOf:
                              - $ref: "#/components/schemas/BusinessCollectionMutationResult"
              examples:
                pending:
                  summary: Created and pending review
                  value:
                    code: 200
                    data:
                      data:
                        id: "7123456789012350"
                        review_status: STATUS_APPROVED
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `name is required (1-256 bytes)`; `provide 1-100 IDs`; `duplicate ID`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_long_name:
                  summary: Blank or long name
                  value:
                    code: 400
                    error: name is required (1-256 bytes)
                    success: false
                more_than_100_ids:
                  summary: More than 100 IDs
                  value:
                    code: 400
                    error: provide 1-100 IDs
                    success: false
                repeated_id:
                  summary: Repeated ID
                  value:
                    code: 400
                    error: duplicate ID
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_product_list:
                  summary: Empty product list
                  value:
                    code: 502
                    error: "business operation failed: business collection product list must contain between 1 and 100 IDs"
                    success: false
  /business/collections/delete:
    post:
      tags:
        - Business
      summary: Delete catalog collections
      description: |
        Deletes collections from the own catalog. The products inside them are not deleted.

        **Rules:**
        - `collection_ids` is required: 1-100 unique IDs of 1-256 bytes (`400 provide 1-100 IDs`, `duplicate ID`, `ID must contain 1-256 bytes`)
        - Use the latest ID of each collection (edits issue new IDs)
        - On success `data.data` is `null` and this user's catalog cache is cleared
      requestBody:
        required: true
        description: Collection IDs to delete (1-100 unique).
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementCollectionsRequest"
            examples:
              single:
                summary: Delete one collection
                description: Deletes one collection by its latest ID.
                value:
                  collection_ids:
                    - "7123456789012350"
              several:
                summary: Delete several collections
                description: Deletes two collections in one call.
                value:
                  collection_ids:
                    - "7123456789012350"
                    - "7123456789012352"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Collections deleted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                deleted:
                  summary: Collections deleted
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `provide 1-100 IDs`; `duplicate ID`; `ID must contain 1-256 bytes`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_more_than_100_ids:
                  summary: Empty or more than 100 IDs
                  value:
                    code: 400
                    error: provide 1-100 IDs
                    success: false
                repeated_id:
                  summary: Repeated ID
                  value:
                    code: 400
                    error: duplicate ID
                    success: false
                blank_or_long_id:
                  summary: Blank or long ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                old_collection_id:
                  summary: Old collection ID
                  value:
                    code: 502
                    error: "business operation failed: delete business collections: business GraphQL request failed with error code(s) 2498052"
                    success: false
  /business/collections/reorder:
    post:
      tags:
        - Business
      summary: Reorder catalog collections
      description: |
        Moves collections to new positions in the own catalog (Meta mutation `xfb_whatsapp_catalog_update_collection_list`).

        **Current live behavior:**
        - WhatsApp currently rejects this mutation: the route responds `502 business operation failed: reorder business collections: business GraphQL request failed with error code(s) 2498055`
        - The contract below is what the gateway validates and sends

        **Rules:**
        - `moves` is required, 1-100 items (`400 provide 1-100 moves`)
        - Each move has `collection_id` (unique across moves, 1-256 bytes), `from_index` and `to_index` (zero-based; negative values return `400 indices must be nonnegative`, values of 100 or more return `502`)
        - On success `data.data` is `null` and this user's catalog cache is cleared
      requestBody:
        required: true
        description: Moves to apply (1-100).
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementMovesRequest"
            examples:
              single:
                summary: Move one collection to the top
                description: Moves the collection at position 2 to position 0.
                value:
                  moves:
                    - collection_id: "7123456789012352"
                      from_index: 2
                      to_index: 0
              swap:
                summary: Swap two collections
                description: Exchanges the first two positions.
                value:
                  moves:
                    - collection_id: "7123456789012350"
                      from_index: 0
                      to_index: 1
                    - collection_id: "7123456789012352"
                      from_index: 1
                      to_index: 0
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Collections reordered
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                reordered:
                  summary: Collections reordered
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `provide 1-100 moves`; `indices must be nonnegative`; `duplicate ID`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_more_than_100_moves:
                  summary: Empty or more than 100 moves
                  value:
                    code: 400
                    error: provide 1-100 moves
                    success: false
                negative_index:
                  summary: Negative index
                  value:
                    code: 400
                    error: indices must be nonnegative
                    success: false
                repeated_collection:
                  summary: Repeated collection
                  value:
                    code: 400
                    error: duplicate ID
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                mutation_rejected_by_whatsapp:
                  summary: Mutation rejected by WhatsApp
                  value:
                    code: 502
                    error: "business operation failed: reorder business collections: business GraphQL request failed with error code(s) 2498055"
                    success: false
  /business/collections/{businessJID}/{collectionID}:
    get:
      tags:
        - Business
      summary: Get one catalog collection with its products
      description: |
        Fetches one collection from the public catalog of `businessJID` (GraphQL `QueryProductSingleCollection`) with a page of its products.

        **Features:**
        - Collection `id`, `name`, review `status_info` (`status`, `can_appeal`, `commerce_url`, `reject_reason` when present)
        - One page of products; pass `next` as the next `cursor`

        **Rules:**
        - `businessJID` must be `@s.whatsapp.net` or `@lid`; `collectionID` 1-256 bytes
        - `limit` 1-100 (default 20); `cursor` at most 2048 bytes
        - Products hidden or under review are not listed
        - Editing a collection issues a NEW ID; the previous ID stops resolving and returns `502` with GraphQL `2498052`
        - Read-only: the catalog cache is not touched
      parameters:
        - name: businessJID
          in: path
          required: true
          description: "Business owner JID: a phone JID (`@s.whatsapp.net`) or LID (`@lid`). Other servers return `400 businessJID must be a user JID`."
          schema:
            type: string
          example: 5511988888888@s.whatsapp.net
        - name: collectionID
          in: path
          required: true
          description: Collection ID, 1-256 bytes. Use the latest ID returned by create/update.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012350"
        - name: limit
          in: query
          required: false
          description: Page size, 1-100 (default 20). Must appear at most once.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          example: 20
        - name: cursor
          in: query
          required: false
          description: Opaque cursor copied from `next` of the previous page. At most once and 2048 bytes.
          schema:
            type: string
            maxLength: 2048
          example: AQHRcursorEXAMPLE
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Collection page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Collection with one page of products.
                            properties:
                              id:
                                type: string
                                description: Collection ID.
                                example: "7123456789012350"
                              name:
                                type: string
                                description: Collection name.
                                example: Chairs
                              next:
                                type: string
                                description: Cursor for the next page of products; omitted on the last page.
                                example: AQHRcursorEXAMPLE
                              previous:
                                type: string
                                description: Cursor for the previous page, when present.
                                example: AQHRcursorPREVIOUS
                              products:
                                type: array
                                description: Products on this page.
                                items:
                                  $ref: "#/components/schemas/BusinessCatalogProduct"
                              status_info:
                                type: object
                                description: Meta review state of the collection.
                                properties:
                                  status:
                                    type: string
                                    description: Review status.
                                    example: APPROVED
                                  can_appeal:
                                    type: boolean
                                    description: True when a rejected collection can be appealed.
                                    example: false
                                  commerce_url:
                                    type: string
                                    description: Commerce policy link, when present.
                                    example: https://www.facebook.com/policies/commerce
                                  reject_reason:
                                    type: string
                                    description: Rejection reason, when rejected.
                                    example: POLICY_VIOLATION
              examples:
                approved:
                  summary: Approved collection with one product
                  value:
                    code: 200
                    data:
                      data:
                        id: "7123456789012350"
                        name: Chairs
                        next: AQHRcursorEXAMPLE
                        products:
                          - id: "7123456789012345"
                            retailer_id: CHAIR-001
                            name: Wooden chair
                            description: Solid wood chair with natural finish.
                            price: "3000000"
                            currency: BRL
                            url: https://example.com/products/chair/
                            is_hidden: false
                            is_sanctioned: false
                            product_availability: in stock
                            compliance_info:
                              country_code_origin: BR
                            media:
                              images:
                                - id: "7123456789012346"
                                  original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                            sale_price:
                              price: "2499900"
                            status_info:
                              status: APPROVED
                        status_info:
                          status: APPROVED
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `ID must contain 1-256 bytes`; `businessJID must be a user JID`; `limit must be between 1 and 100`; `cursor must be provided once and contain at most 2048 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
                businessjid_is_not_a_user_jid:
                  summary: businessJID is not a user JID
                  value:
                    code: 400
                    error: businessJID must be a user JID
                    success: false
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 100
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                old_or_unknown_collection_id:
                  summary: Old or unknown collection ID
                  value:
                    code: 502
                    error: "business operation failed: QueryProductSingleCollection: 2498052  (ERROR)"
                    success: false
  /business/collections/{collectionID}:
    put:
      tags:
        - Business
      summary: Edit a catalog collection
      description: |
        Edits a collection of the own catalog: rename it and/or add and remove products.

        **Versioning:**
        - A successful edit returns a NEW `id` in `data.data.id`; use it from then on
        - The previous ID stops resolving (`GET /business/collections/{businessJID}/{oldId}` returns `502` with GraphQL `2498052`)
        - The edited collection is reviewed by Meta again (`review_status`)

        **Rules:**
        - At least one of `name`, `add_product_ids`, `remove_product_ids` (`400 collection update is empty`)
        - `name`, when present, 1-256 bytes; omit it to keep the current name
        - `add_product_ids` / `remove_product_ids`: each 0-100 unique IDs of 1-256 bytes
        - The same ID in both lists returns `502 business operation failed: product ID "<id>" cannot be added and removed together`
        - On success this user's catalog cache is cleared
      parameters:
        - name: collectionID
          in: path
          required: true
          description: Current collection ID (the latest one returned by create/update), 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012350"
      requestBody:
        required: true
        description: Fields to change; at least one is required.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/types.BusinessCollectionUpdate"
            examples:
              rename:
                summary: Rename only
                description: Changes the name and keeps the products.
                value:
                  name: Dining chairs
              products:
                summary: Add and remove products
                description: Swaps one product for another.
                value:
                  add_product_ids:
                    - "7123456789012346"
                  remove_product_ids:
                    - "7123456789012345"
              full:
                summary: Rename and add products
                description: Renames the collection and adds two products.
                value:
                  name: Dining chairs
                  add_product_ids:
                    - "7123456789012346"
                    - "7123456789012347"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Collection edited (new ID)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: New collection ID and review status.
                            allOf:
                              - $ref: "#/components/schemas/BusinessCollectionMutationResult"
              examples:
                new_id:
                  summary: New collection ID after the edit
                  value:
                    code: 200
                    data:
                      data:
                        id: "7123456789012351"
                        review_status: STATUS_APPROVED
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `collection update is empty`; `provide 1-100 IDs`; `duplicate ID`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`; `ID must contain 1-256 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                nothing_to_change:
                  summary: Nothing to change
                  value:
                    code: 400
                    error: collection update is empty
                    success: false
                more_than_100_ids:
                  summary: More than 100 IDs
                  value:
                    code: 400
                    error: provide 1-100 IDs
                    success: false
                repeated_id:
                  summary: Repeated ID
                  value:
                    code: 400
                    error: duplicate ID
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                same_id_added_and_removed:
                  summary: Same ID added and removed
                  value:
                    code: 502
                    error: 'business operation failed: product ID "7123456789012345" cannot be added and removed together'
                    success: false
  /business/collections/{collectionID}/appeal:
    post:
      tags:
        - Business
      summary: Appeal a collection review decision
      description: |
        Requests a new Meta review of an own collection, typically one rejected in review (`status_info.can_appeal: true`, `reject_reason`).

        **Rules:**
        - `reason` is required, non-blank and at most 1024 bytes (`400 reason must contain 1-1024 bytes`); it is trimmed before sending
        - Use the latest collection ID (edits issue new IDs)
        - Upstream refusals return `502`
        - On success `data.data` is `null` and this user's catalog cache is cleared
      parameters:
        - name: collectionID
          in: path
          required: true
          description: Own collection ID (latest version), 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012350"
      requestBody:
        required: true
        description: Appeal reason shown to the Meta reviewer.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementAppealRequest"
            examples:
              reason:
                summary: Appeal reason
                description: Explains why the collection complies with the commerce policies.
                value:
                  reason: The collection only groups handmade furniture that complies with the commerce policies.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Appeal submitted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                submitted:
                  summary: Appeal accepted
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `reason must contain 1-1024 bytes`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`; `ID must contain 1-256 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_long_reason:
                  summary: Blank or long reason
                  value:
                    code: 400
                    error: reason must contain 1-1024 bytes
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                collection_cannot_be_appealed:
                  summary: Collection cannot be appealed
                  value:
                    code: 502
                    error: "business operation failed: appeal business collection: business GraphQL request failed with error code(s) 2498052"
                    success: false
  /business/cover:
    post:
      tags:
        - Business
      summary: Set the business cover photo
      description: |
        Uploads a JPEG or PNG image and sets it as the cover photo of the connected business profile. Returns `data.data.cover_id`, used later by `DELETE /business/cover/{coverID}`.

        **Rules:**
        - `image` is required, standard base64 of the raw bytes without a `data:` URI prefix
        - The gateway accepts 1 byte to 16 MiB of JPEG/PNG (`400 image must contain 1-16777216 bytes` / `image must be JPEG or PNG`), but WhatsApp's library limits cover photos to **5 MiB**: larger images return `502`
        - Do not retry blindly after a `5xx`: the upload may already have been accepted
        - On success this user's catalog cache is cleared
      requestBody:
        required: true
        description: Cover photo bytes in standard base64 (up to 5 MiB accepted by WhatsApp).
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementImageRequest"
            examples:
              png:
                summary: PNG image
                description: A 1x1 PNG encoded in base64 (replace with the real cover photo).
                value:
                  image: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Cover photo set
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Cover photo reference.
                            properties:
                              cover_id:
                                type: string
                                description: Cover photo ID for `DELETE /business/cover/{coverID}`.
                                example: "7123456789012348"
              examples:
                set:
                  summary: Cover photo ID
                  value:
                    code: 200
                    data:
                      data:
                        cover_id: "7123456789012348"
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `image must contain 1-16777216 bytes`; `image must be JPEG or PNG`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_larger_than_16_mib:
                  summary: Empty or larger than 16 MiB
                  value:
                    code: 400
                    error: image must contain 1-16777216 bytes
                    success: false
                not_jpeg_or_png:
                  summary: Not JPEG or PNG
                  value:
                    code: 400
                    error: image must be JPEG or PNG
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                larger_than_5_mib:
                  summary: Larger than 5 MiB
                  value:
                    code: 502
                    error: "business operation failed: business cover photo exceeds 5242880 bytes"
                    success: false
  /business/cover/{coverID}:
    delete:
      tags:
        - Business
      summary: Delete the business cover photo
      description: |
        Removes the cover photo of the connected business profile (IQ `business_profile` / `cover_photo op=delete`).

        **Rules:**
        - `coverID` is the `cover_id` returned by `POST /business/cover`, 1-256 bytes (`400 ID must contain 1-256 bytes`)
        - No body is read
        - On success `data.data` is `null` and this user's catalog cache is cleared
      parameters:
        - name: coverID
          in: path
          required: true
          description: Cover photo ID (`cover_id` from `POST /business/cover`), 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012348"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Cover photo deleted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                deleted:
                  summary: Cover photo removed
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `ID must contain 1-256 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unknown_cover_id:
                  summary: Unknown cover ID
                  value:
                    code: 502
                    error: "business operation failed: failed to delete business cover photo: info query returned status 404: item-not-found"
                    success: false
  /business/eligibility:
    post:
      tags:
        - Business
      summary: Check business feature eligibility
      description: |
        Asks WhatsApp whether the connected business account is eligible for the requested features (IQ `features`).

        **Features:**
        - Checks up to 6 features in one call: `meta_verified`, `marketing_messages`, `genai`, `genai_image`, `meta_one`, `bb_pro`
        - Returns one entry per feature with `status` and, when sent by the server, `expiration`, `additional_params`, `show_privacy_interstitial_to_new_users` and `v1_enabled`

        **Rules:**
        - `features` is required: 1-6 known values (`400 provide 1-6 features` / `unknown business feature`)
        - Duplicated features pass the gateway but WhatsApp's library refuses them with `502 business operation failed: duplicate business feature "<name>"`
        - Accepted statuses: `marketing_messages` → `FAIL`, `PAUSED`, `SUCCESS`, `WARNING`; `bb_pro` → `ELIGIBLE_TO_ONBOARD`, `NOT_ELIGIBLE`, `ONBOARDED`; other features → `FAIL`, `SUCCESS`. Any other status fails with `502`
        - Read-only: the catalog cache is not touched
      requestBody:
        required: true
        description: Features to check (1-6 known values).
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementEligibilityRequest"
            examples:
              one:
                summary: Single feature
                description: Check only Meta Verified eligibility.
                value:
                  features:
                    - meta_verified
              all:
                summary: All features
                description: Check every supported feature in one call.
                value:
                  features:
                    - meta_verified
                    - marketing_messages
                    - genai
                    - genai_image
                    - meta_one
                    - bb_pro
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Eligibility per requested feature
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Eligibility result.
                            properties:
                              features:
                                type: array
                                description: One entry per requested feature.
                                items:
                                  type: object
                                  properties:
                                    feature:
                                      type: string
                                      description: Feature name.
                                      example: meta_verified
                                    status:
                                      type: string
                                      description: Eligibility status for this feature.
                                      example: SUCCESS
                                    expiration:
                                      type: integer
                                      description: Expiration as a Unix timestamp, when sent.
                                      example: 1767225600
                                    additional_params:
                                      type: string
                                      description: Opaque extra parameters, when sent.
                                      example: "{}"
                                    show_privacy_interstitial_to_new_users:
                                      type: boolean
                                      description: Privacy interstitial flag, when sent.
                                      example: false
                                    v1_enabled:
                                      type: boolean
                                      description: Version flag, when sent.
                                      example: true
              examples:
                mixed:
                  summary: Two features checked
                  value:
                    code: 200
                    data:
                      data:
                        features:
                          - feature: meta_verified
                            status: SUCCESS
                          - feature: bb_pro
                            status: ELIGIBLE_TO_ONBOARD
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `provide 1-6 features`; `unknown business feature`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_features_or_more_than_6:
                  summary: No features or more than 6
                  value:
                    code: 400
                    error: provide 1-6 features
                    success: false
                unknown_feature:
                  summary: Unknown feature
                  value:
                    code: 400
                    error: unknown business feature
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                duplicated_feature:
                  summary: Duplicated feature
                  value:
                    code: 502
                    error: 'business operation failed: duplicate business feature "genai"'
                    success: false
  /business/linked-accounts:
    get:
      tags:
        - Business
      summary: Get Meta accounts linked to the business
      description: |
        Returns the Meta assets linked to the connected WhatsApp Business account (IQ `linked_accounts`).

        **Features:**
        - `facebook_page`: page ID, name, picture, ad flags and whether it is shown on the profile
        - `facebook_business`: business portfolio with `catalog_id` / `catalog_state` when a catalog is attached
        - `instagram_professional`: handle, name, picture and profile visibility
        - `whatsapp_ad_identity`: ad identity ID and ad flags

        **Rules:**
        - No body and no query parameters
        - Each key is omitted when the asset is not linked, so an account without links returns `data.data: {}`
        - Read-only: the catalog cache is not touched

        **Use Cases:**
        - Check whether a Facebook page or Instagram account is connected before running click-to-WhatsApp ads
        - Find the Meta catalog ID attached to the business portfolio
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Linked Meta assets
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Linked assets; keys are omitted when not linked.
                            properties:
                              facebook_page:
                                type: object
                                description: Linked Facebook page.
                                properties:
                                  id:
                                    type: string
                                    description: Page ID.
                                    example: "7123456789012360"
                                  display_name:
                                    type: string
                                    description: Page name.
                                    example: Example Store
                                  profile_sync:
                                    type: string
                                    description: Profile sync state, when reported.
                                    example: ENABLED
                                  has_active_ctwa_ad:
                                    type: boolean
                                    description: True when a click-to-WhatsApp ad is running.
                                    example: false
                                  has_created_ad:
                                    type: boolean
                                    description: True when an ad was ever created.
                                    example: false
                                  profile_picture_url:
                                    type: string
                                    description: Page picture URL.
                                    example: https://example.com/page-picture.jpg
                                  show_on_profile:
                                    type: boolean
                                    description: True when shown on the WhatsApp profile.
                                    example: true
                                  whatsapp_as_page_button:
                                    type: boolean
                                    description: True when the page uses WhatsApp as its button.
                                    example: false
                              facebook_business:
                                type: object
                                description: Linked business portfolio.
                                properties:
                                  id:
                                    type: string
                                    description: Business portfolio ID.
                                    example: "7123456789012361"
                                  display_name:
                                    type: string
                                    description: Portfolio name.
                                    example: Example Store
                                  catalog_id:
                                    type: string
                                    description: Attached catalog ID, when present.
                                    example: "7123456789012300"
                                  catalog_state:
                                    type: string
                                    description: Catalog state, when present.
                                    example: ACTIVE
                              instagram_professional:
                                type: object
                                description: Linked Instagram professional account.
                                properties:
                                  handle:
                                    type: string
                                    description: Instagram handle.
                                    example: examplestore
                                  display_name:
                                    type: string
                                    description: Account name.
                                    example: Example Store
                                  profile_picture_url:
                                    type: string
                                    description: Picture URL.
                                    example: https://example.com/instagram-picture.jpg
                                  show_on_profile:
                                    type: boolean
                                    description: True when shown on the WhatsApp profile.
                                    example: true
                              whatsapp_ad_identity:
                                type: object
                                description: WhatsApp ad identity.
                                properties:
                                  id:
                                    type: string
                                    description: Ad identity ID.
                                    example: "7123456789012362"
                                  has_active_ctwa_ad:
                                    type: boolean
                                    description: True when a click-to-WhatsApp ad is running.
                                    example: false
                                  has_created_ad:
                                    type: boolean
                                    description: True when an ad was ever created.
                                    example: true
              examples:
                page_and_instagram:
                  summary: Facebook page and Instagram linked
                  value:
                    code: 200
                    data:
                      data:
                        facebook_page:
                          id: "7123456789012360"
                          display_name: Example Store
                          has_active_ctwa_ad: false
                          has_created_ad: false
                          profile_picture_url: https://example.com/page-picture.jpg
                          show_on_profile: true
                          whatsapp_as_page_button: false
                        instagram_professional:
                          handle: examplestore
                          display_name: Example Store
                          profile_picture_url: https://example.com/instagram-picture.jpg
                          show_on_profile: true
                      success: true
                    success: true
                no_links:
                  summary: No linked assets
                  value:
                    code: 200
                    data:
                      data: {}
                      success: true
                    success: true
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                iq_failed:
                  summary: IQ failed
                  value:
                    code: 502
                    error: "business operation failed: get linked business accounts: info query timed out"
                    success: false
  /business/merchant-compliance:
    get:
      tags:
        - Business
      summary: Get merchant compliance information
      description: |
        Reads the merchant compliance record of the connected business account, as shown in the WhatsApp Business compliance settings.

        **Features:**
        - Legal entity name and type, registration flag
        - Customer care contact and grievance officer

        **Rules:**
        - No body; uses the Meta GraphQL endpoint
        - If the server response has no `merchant_info` the call fails with `502`
        - Read-only: the catalog cache is not touched
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Merchant compliance record
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: Merchant compliance record.
                            allOf:
                              - $ref: "#/components/schemas/types.BusinessMerchantCompliance"
              examples:
                registered_company:
                  summary: Registered private company
                  value:
                    code: 200
                    data:
                      data:
                        entity_name: Example Store Ltd
                        entity_type: PRIVATE_COMPANY
                        is_registered: true
                        entity_type_custom: ""
                        customer_care_details:
                          email: support@example.com
                          landline_number: ""
                          mobile_number: "5511999999999"
                        grievance_officer_details:
                          name: Jane Doe
                          email: compliance@example.com
                          landline_number: ""
                          mobile_number: "5511988888888"
                      success: true
                    success: true
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                response_without_merchant_info:
                  summary: Response without merchant_info
                  value:
                    code: 502
                    error: "business operation failed: business merchant compliance response is missing merchant_info"
                    success: false
    put:
      tags:
        - Business
      summary: Set merchant compliance information
      description: |
        Replaces the merchant compliance record of the connected business account and returns the record stored by the server.

        **Rules:**
        - `entity_name` is required, 1-256 bytes after trimming; the gateway reports a blank or oversized value as `400 ID must contain 1-256 bytes`
        - `entity_type` is required: `SOLE_PROPRIETORSHIP`, `PARTNERSHIP`, `PRIVATE_COMPANY`, `PUBLIC_COMPANY`, `LIMITED_LIABILITY_PARTNERSHIP` or `OTHER`; `OTHER` requires `entity_type_custom`
        - Limits: names 256 bytes, emails 254 bytes, phone numbers 64 bytes; all strings are trimmed
        - The whole object is sent: omitted nested fields are sent as empty strings
        - Library validation (missing or unsupported `entity_type`, missing custom type, lengths) returns `502 business operation failed: business merchant …`
        - On success this user's catalog cache is cleared
      requestBody:
        required: true
        description: Complete merchant compliance record.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/types.BusinessMerchantCompliance"
            examples:
              company:
                summary: Registered private company
                description: Private company with customer care and grievance officer contacts.
                value:
                  entity_name: Example Store Ltd
                  entity_type: PRIVATE_COMPANY
                  is_registered: true
                  customer_care_details:
                    email: support@example.com
                    landline_number: ""
                    mobile_number: "5511999999999"
                  grievance_officer_details:
                    name: Jane Doe
                    email: compliance@example.com
                    landline_number: ""
                    mobile_number: "5511988888888"
              other:
                summary: Custom entity type (OTHER)
                description: Unregistered cooperative using `entity_type_custom`.
                value:
                  entity_name: Example Cooperative
                  entity_type: OTHER
                  is_registered: false
                  entity_type_custom: Cooperative
                  customer_care_details:
                    email: support@example.com
                    landline_number: ""
                    mobile_number: "5511999999999"
                  grievance_officer_details:
                    name: Jane Doe
                    email: compliance@example.com
                    landline_number: ""
                    mobile_number: "5511988888888"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Stored merchant compliance record
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: Merchant compliance record.
                            allOf:
                              - $ref: "#/components/schemas/types.BusinessMerchantCompliance"
              examples:
                stored:
                  summary: Record stored by the server
                  value:
                    code: 200
                    data:
                      data:
                        entity_name: Example Store Ltd
                        entity_type: PRIVATE_COMPANY
                        is_registered: true
                        entity_type_custom: ""
                        customer_care_details:
                          email: support@example.com
                          landline_number: ""
                          mobile_number: "5511999999999"
                        grievance_officer_details:
                          name: Jane Doe
                          email: compliance@example.com
                          landline_number: ""
                          mobile_number: "5511988888888"
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `ID must contain 1-256 bytes`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_long_entity_name:
                  summary: Blank or long entity_name
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unsupported_entity_type:
                  summary: Unsupported entity type
                  value:
                    code: 502
                    error: 'business operation failed: unsupported business merchant entity type "LLC"'
                    success: false
                other_without_custom_type:
                  summary: OTHER without custom type
                  value:
                    code: 502
                    error: "business operation failed: business merchant custom entity type is empty"
                    success: false
  /business/products:
    post:
      tags:
        - Business
      summary: Create a catalog product
      description: |
        Creates a product in the own catalog of the connected session and returns it in `data.data` with its new `id`.

        **Price and currency:**
        - `price` and `sale_price` are strings with integer thousandths of the currency, no separators: `"3000000"` = 3,000.00; `"2499900"` = 2,499.90
        - `currency` (ISO-4217 uppercase) is required with `price`; `currency` or `sale_price` without `price` returns `502`

        **Media:**
        - `image_urls`: 1-10 HTTPS WhatsApp/Meta media URLs, normally the `image_id` returned by `POST /business/products/image`; the first one is the main photo
        - Other hosts return `502 business operation failed: invalid business product image URL: …`

        **Country of origin:**
        - Optional in the API. Send `compliance_info.country_code_origin` (ISO-3166 alpha-2) with optional `importer_name` / `importer_address`
        - When it does not apply, send `compliance_category: "COUNTRY_ORIGIN_EXEMPT"` without `compliance_info` ("Not applicable" in the app)

        **Validation:**
        - Gateway `400`: `product.name` blank or over 256 bytes, `width`/`height` outside 0 or 1-1024, `image_urls` count outside 1-10, unknown fields (also inside `product`)
        - WhatsApp library `502`: media hosts, `url` not absolute HTTPS ≤ 2048 bytes, price/currency format, `description` > 4096 bytes, `retailer_id` > 256 bytes, more than 10 `video_urls`, compliance field lengths

        **Review:**
        - Created or edited products go through Meta review (`status_info.status`: `NO_REVIEW`, `PENDING`, `APPROVED`, `REJECTED`)
        - While pending or hidden the product is missing from public reads and collections; `GET /business/catalog/own` shows it

        **Behavior:**
        - `is_hidden: true` creates the product already hidden
        - `width` / `height` only size the media URLs returned in the response (default 100)
        - On success this user's catalog cache is cleared
      requestBody:
        required: true
        description: Product to create plus optional response media size.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementProductRequest"
            examples:
              with_country_of_origin:
                summary: Product with sale price and country of origin
                description: "Full product: price, sale price, retailer ID, one uploaded photo and country of origin."
                value:
                  product:
                    name: Wooden chair
                    description: Solid wood chair with natural finish.
                    url: https://example.com/products/chair
                    retailer_id: CHAIR-001
                    currency: BRL
                    price: "3000000"
                    sale_price: "2499900"
                    is_hidden: false
                    image_urls:
                      - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                    compliance_info:
                      country_code_origin: BR
              country_exempt:
                summary: Country of origin not applicable
                description: Service product using `COUNTRY_ORIGIN_EXEMPT` without `compliance_info`.
                value:
                  product:
                    name: Assembly service
                    currency: BRL
                    price: "150000"
                    is_hidden: false
                    image_urls:
                      - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                    compliance_category: COUNTRY_ORIGIN_EXEMPT
              hidden_draft:
                summary: Hidden draft without price
                description: Creates a hidden product without price; `currency` must also be omitted.
                value:
                  product:
                    name: Oak table (draft)
                    is_hidden: true
                    image_urls:
                      - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                  width: 640
                  height: 640
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Product created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: Created product (review usually `PENDING`).
                            allOf:
                              - $ref: "#/components/schemas/BusinessCatalogProduct"
              examples:
                pending_review:
                  summary: Created and pending review
                  value:
                    code: 200
                    data:
                      data:
                        id: "7123456789012345"
                        retailer_id: CHAIR-001
                        name: Wooden chair
                        description: Solid wood chair with natural finish.
                        price: "3000000"
                        currency: BRL
                        url: https://example.com/products/chair/
                        is_hidden: false
                        is_sanctioned: false
                        product_availability: in stock
                        compliance_info:
                          country_code_origin: BR
                        media:
                          images:
                            - id: "7123456789012346"
                              original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                        sale_price:
                          price: "2499900"
                        status_info:
                          status: PENDING
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `product name must contain 1-256 bytes`; `dimensions must be 0 (default) or 1-1024`; `product requires 1-10 images`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_long_name:
                  summary: Blank or long name
                  value:
                    code: 400
                    error: product name must contain 1-256 bytes
                    success: false
                invalid_media_size:
                  summary: Invalid media size
                  value:
                    code: 400
                    error: dimensions must be 0 (default) or 1-1024
                    success: false
                no_images_or_more_than_10:
                  summary: No images or more than 10
                  value:
                    code: 400
                    error: product requires 1-10 images
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                image_host_not_allowed:
                  summary: Image host not allowed
                  value:
                    code: 502
                    error: "business operation failed: invalid business product image URL: URL must use a WhatsApp or Meta media host"
                    success: false
                price_without_currency:
                  summary: Price without currency
                  value:
                    code: 502
                    error: "business operation failed: business product currency must be a three-letter uppercase code"
                    success: false
  /business/products/delete:
    post:
      tags:
        - Business
      summary: Delete catalog products
      description: |
        Deletes products from the own catalog in one call. Deletion is not reversible.

        **Rules:**
        - `product_ids` is required: 1-100 unique IDs of 1-256 bytes (`400 provide 1-100 IDs`, `duplicate ID`, `ID must contain 1-256 bytes`)
        - `data.data.deleted` is the number the server reports as deleted; it can be lower than the IDs sent (for example IDs that no longer exist)
        - On success this user's catalog cache is cleared
      requestBody:
        required: true
        description: Own product IDs to delete (1-100 unique).
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementIDsRequest"
            examples:
              single:
                summary: Delete one product
                description: Removes a single product.
                value:
                  product_ids:
                    - "7123456789012345"
              several:
                summary: Delete several products
                description: Removes two products in one call.
                value:
                  product_ids:
                    - "7123456789012345"
                    - "7123456789012346"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Products deleted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Deletion count.
                            properties:
                              deleted:
                                type: integer
                                description: Number of products the server reports as deleted.
                                example: 2
              examples:
                two_deleted:
                  summary: Two products deleted
                  value:
                    code: 200
                    data:
                      data:
                        deleted: 2
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `provide 1-100 IDs`; `duplicate ID`; `ID must contain 1-256 bytes`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_more_than_100_ids:
                  summary: Empty or more than 100 IDs
                  value:
                    code: 400
                    error: provide 1-100 IDs
                    success: false
                repeated_id:
                  summary: Repeated ID
                  value:
                    code: 400
                    error: duplicate ID
                    success: false
                blank_or_long_id:
                  summary: Blank or long ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                graphql_request_timed_out:
                  summary: GraphQL request timed out
                  value:
                    code: 502
                    error: "business operation failed: delete business products: execute business GraphQL request: context deadline exceeded"
                    success: false
  /business/products/image:
    post:
      tags:
        - Business
      summary: Upload a catalog product image
      description: |
        Uploads one JPEG or PNG photo and returns `data.data.image_id`, the HTTPS media URL to use in `image_urls` of `POST /business/products` and `PUT /business/products/{productID}`.

        **Rules:**
        - `image` is standard base64 of the raw bytes, without a `data:` URI prefix (a prefix makes JSON decoding fail with `400`)
        - Empty, larger than 16 MiB or not JPEG/PNG → `400`
        - Send one call per photo; do not retry automatically after an uncertain `5xx`, the upload may have been accepted
        - On success this user's catalog cache is cleared
      requestBody:
        required: true
        description: Photo bytes in standard base64.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementImageRequest"
            examples:
              png:
                summary: PNG image
                description: A 1x1 PNG encoded in base64 (replace with the real photo).
                value:
                  image: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Image uploaded
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Uploaded media reference.
                            properties:
                              image_id:
                                type: string
                                description: HTTPS media URL to use in `image_urls`.
                                example: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
              examples:
                uploaded:
                  summary: Uploaded photo URL
                  value:
                    code: 200
                    data:
                      data:
                        image_id: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `image must contain 1-16777216 bytes`; `image must be JPEG or PNG`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                empty_or_larger_than_16_mib:
                  summary: Empty or larger than 16 MiB
                  value:
                    code: 400
                    error: image must contain 1-16777216 bytes
                    success: false
                not_jpeg_or_png:
                  summary: Not JPEG or PNG
                  value:
                    code: 400
                    error: image must be JPEG or PNG
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                upload_host_unavailable:
                  summary: Upload host unavailable
                  value:
                    code: 502
                    error: "business operation failed: media connection response contained no upload hosts"
                    success: false
  /business/products/{productID}:
    put:
      tags:
        - Business
      summary: Replace a catalog product
      description: |
        Replaces a product of the own catalog and returns the updated product in `data.data`.

        **Replace semantics:**
        - The PUT replaces `product_info` entirely: every omitted field is REMOVED (no `sale_price` removes the sale price; no `compliance_info` removes the country of origin)
        - `is_hidden` is always sent, so omitting it makes the product visible
        - Read the product first with `GET /business/catalog/own` and send it complete
        - On read the server normalizes `url` with a trailing slash

        **Price and currency:**
        - `price` and `sale_price` are strings with integer thousandths of the currency, no separators: `"3000000"` = 3,000.00; `"2499900"` = 2,499.90
        - `currency` (ISO-4217 uppercase) is required with `price`; `currency` or `sale_price` without `price` returns `502`

        **Media:**
        - `image_urls`: 1-10 HTTPS WhatsApp/Meta media URLs, normally the `image_id` returned by `POST /business/products/image`; the first one is the main photo
        - Other hosts return `502 business operation failed: invalid business product image URL: …`

        **Country of origin:**
        - Optional in the API. Send `compliance_info.country_code_origin` (ISO-3166 alpha-2) with optional `importer_name` / `importer_address`
        - When it does not apply, send `compliance_category: "COUNTRY_ORIGIN_EXEMPT"` without `compliance_info` ("Not applicable" in the app)

        **Validation:**
        - Gateway `400`: `product.name` blank or over 256 bytes, `width`/`height` outside 0 or 1-1024, `image_urls` count outside 1-10, unknown fields (also inside `product`)
        - WhatsApp library `502`: media hosts, `url` not absolute HTTPS ≤ 2048 bytes, price/currency format, `description` > 4096 bytes, `retailer_id` > 256 bytes, more than 10 `video_urls`, compliance field lengths

        **Review:**
        - Created or edited products go through Meta review (`status_info.status`: `NO_REVIEW`, `PENDING`, `APPROVED`, `REJECTED`)
        - While pending or hidden the product is missing from public reads and collections; `GET /business/catalog/own` shows it

        **Behavior:**
        - On success this user's catalog cache is cleared
      parameters:
        - name: productID
          in: path
          required: true
          description: ID of the own catalog product to replace, 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012345"
      requestBody:
        required: true
        description: Complete product; omitted fields are removed.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementProductRequest"
            examples:
              full_replace:
                summary: Complete product
                description: Sends every field so nothing is removed.
                value:
                  product:
                    name: Wooden chair
                    description: Solid wood chair with natural finish.
                    url: https://example.com/products/chair
                    retailer_id: CHAIR-001
                    currency: BRL
                    price: "3000000"
                    sale_price: "2499900"
                    is_hidden: false
                    image_urls:
                      - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                    compliance_info:
                      country_code_origin: BR
              remove_sale_price:
                summary: Remove the sale price
                description: "Same product without `sale_price`: the sale price is removed."
                value:
                  product:
                    name: Wooden chair
                    description: Solid wood chair with natural finish.
                    url: https://example.com/products/chair
                    retailer_id: CHAIR-001
                    currency: BRL
                    price: "3000000"
                    is_hidden: false
                    image_urls:
                      - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                    compliance_info:
                      country_code_origin: BR
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Product replaced
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            description: Updated product.
                            allOf:
                              - $ref: "#/components/schemas/BusinessCatalogProduct"
              examples:
                updated:
                  summary: Product after the edit
                  value:
                    code: 200
                    data:
                      data:
                        id: "7123456789012345"
                        retailer_id: CHAIR-001
                        name: Wooden chair
                        description: Solid wood chair with natural finish.
                        price: "3000000"
                        currency: BRL
                        url: https://example.com/products/chair/
                        is_hidden: false
                        is_sanctioned: false
                        product_availability: in stock
                        compliance_info:
                          country_code_origin: BR
                        media:
                          images:
                            - id: "7123456789012346"
                              original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                        sale_price:
                          price: "2499900"
                        status_info:
                          status: PENDING
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `product name must contain 1-256 bytes`; `dimensions must be 0 (default) or 1-1024`; `product requires 1-10 images`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`; `ID must contain 1-256 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_long_name:
                  summary: Blank or long name
                  value:
                    code: 400
                    error: product name must contain 1-256 bytes
                    success: false
                invalid_media_size:
                  summary: Invalid media size
                  value:
                    code: 400
                    error: dimensions must be 0 (default) or 1-1024
                    success: false
                no_images_or_more_than_10:
                  summary: No images or more than 10
                  value:
                    code: 400
                    error: product requires 1-10 images
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                image_host_not_allowed:
                  summary: Image host not allowed
                  value:
                    code: 502
                    error: "business operation failed: invalid business product image URL: URL must use a WhatsApp or Meta media host"
                    success: false
                price_without_currency:
                  summary: Price without currency
                  value:
                    code: 502
                    error: "business operation failed: business product currency must be a three-letter uppercase code"
                    success: false
  /business/products/{productID}/appeal:
    post:
      tags:
        - Business
      summary: Appeal a product review decision
      description: |
        Requests a new Meta review of an own catalog product, typically one rejected in review (`status_info.status: REJECTED`, `status_info.can_appeal: true`).

        **Rules:**
        - `reason` is required, non-blank and at most 1024 bytes (`400 reason must contain 1-1024 bytes`); it is trimmed before sending
        - Upstream refusals (for example a product that cannot be appealed) return `502`
        - On success `data.data` is `null` and this user's catalog cache is cleared
      parameters:
        - name: productID
          in: path
          required: true
          description: Own catalog product ID, 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012345"
      requestBody:
        required: true
        description: Appeal reason shown to the Meta reviewer.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementAppealRequest"
            examples:
              reason:
                summary: Appeal reason
                description: Explains why the product complies with the commerce policies.
                value:
                  reason: The product is a handmade wooden chair and complies with the commerce policies.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Appeal submitted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                submitted:
                  summary: Appeal accepted
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `reason must contain 1-1024 bytes`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`; `ID must contain 1-256 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                blank_or_long_reason:
                  summary: Blank or long reason
                  value:
                    code: 400
                    error: reason must contain 1-1024 bytes
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                product_cannot_be_appealed:
                  summary: Product cannot be appealed
                  value:
                    code: 502
                    error: "business operation failed: appeal business product: business GraphQL request failed with error code(s) 2498052"
                    success: false
  /business/products/{productID}/visibility:
    put:
      tags:
        - Business
      summary: Hide or show a catalog product
      description: |
        Changes only the visibility of an own catalog product, without replacing its other fields.

        **Rules:**
        - `hidden` is required (`400 hidden is required`)
        - Hidden products are not exposed by public catalog reads (GraphQL `2498052`) but remain in `GET /business/catalog/own`
        - On success `data.data` is `null` and this user's catalog cache is cleared
      parameters:
        - name: productID
          in: path
          required: true
          description: Own catalog product ID, 1-256 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 256
          example: "7123456789012345"
      requestBody:
        required: true
        description: Target visibility.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessManagementHiddenRequest"
            examples:
              hide:
                summary: Hide product
                description: Removes the product from public catalog reads.
                value:
                  hidden: true
              show:
                summary: Show product
                description: Makes the product visible again.
                value:
                  hidden: false
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Visibility updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                updated:
                  summary: Visibility changed
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `hidden is required`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`; `ID must contain 1-256 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                hidden_missing:
                  summary: hidden missing
                  value:
                    code: 400
                    error: hidden is required
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                blank_or_oversized_path_id:
                  summary: Blank or oversized path ID
                  value:
                    code: 400
                    error: ID must contain 1-256 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unknown_or_deleted_product:
                  summary: Unknown or deleted product
                  value:
                    code: 502
                    error: "business operation failed: update business product visibility: business GraphQL request failed with error code(s) 2498052"
                    success: false
  /business/profile:
    put:
      tags:
        - Business
      summary: Update business profile fields
      description: |
        Applies a partial update to the connected business profile: address, email, description, websites and business hours.

        **Rules:**
        - At least one field is required (`400 profile update is empty`); omitted or `null` fields stay unchanged
        - `address` ≤ 512 bytes; `description` ≤ 1024 bytes
        - `email` ≤ 320 bytes, plain address (`user@example.com`); an empty string clears it
        - `websites`: at most 2 absolute `http`/`https` URLs of ≤ 2048 bytes; `[]` clears them
        - `hours`: `timeZone` (IANA) and up to 7 unique `days` (`sun`…`sat`) with `mode` `specific_hours`, `open_24h` or `appointment_only`; `openTime`/`closeTime` are minutes after midnight (0-1439, different) and only valid with `specific_hours`
        - Keys match case-insensitively (`address` or `Address`); snake_case variants such as `time_zone` are unknown fields and return `400`
        - Library validation failures return `502`; on success `data.data` is `null` and this user's catalog cache is cleared
      requestBody:
        required: true
        description: Profile fields to change; at least one is required.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/types.BusinessProfileUpdate"
            examples:
              contact:
                summary: Contact fields
                description: Updates address, email, description and website.
                value:
                  address: 100 Example Street, Springfield, IL
                  email: contact@example.com
                  description: Handmade wooden furniture.
                  websites:
                    - https://example.com
              hours:
                summary: Business hours only
                description: Weekday hours, Saturday by appointment and Sunday open 24 hours.
                value:
                  hours:
                    timeZone: America/Sao_Paulo
                    days:
                      - dayOfWeek: mon
                        mode: specific_hours
                        openTime: 540
                        closeTime: 1080
                      - dayOfWeek: sat
                        mode: appointment_only
                      - dayOfWeek: sun
                        mode: open_24h
              clear:
                summary: Clear email and websites
                description: An empty email and an empty website list remove both.
                value:
                  email: ""
                  websites: []
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Profile updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            nullable: true
                            description: "Always `null`: the operation returns no payload."
                            example: null
              examples:
                updated:
                  summary: Profile fields applied
                  value:
                    code: 200
                    data:
                      data: null
                      success: true
                    success: true
        "400":
          description: "Request rejected before contacting WhatsApp: `profile update is empty`; `invalid JSON body (maximum 24 MiB)`; `JSON object is required`; `exactly one JSON object is required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_field_sent:
                  summary: No field sent
                  value:
                    code: 400
                    error: profile update is empty
                    success: false
                malformed_or_unknown_field:
                  summary: Malformed or unknown field
                  value:
                    code: 400
                    error: invalid JSON body (maximum 24 MiB)
                    success: false
                json_null_instead_of_an_object:
                  summary: JSON null instead of an object
                  value:
                    code: 400
                    error: JSON object is required
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: WhatsApp rejected the operation or its response failed validation. The upstream reason follows the `business operation failed:` prefix.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                update_rejected:
                  summary: Update rejected
                  value:
                    code: 502
                    error: "business operation failed: failed to update business profile: info query returned status 400: bad-request"
                    success: false
  /chat/retry/message:
    post:
      tags:
        - Chat
      summary: Ask the phone to resend a message
      description: |
        Asks the **primary phone** to resend a message that this linked device could not decrypt or process (for example after an `UndecryptableMessage` event).

        The gateway sends a peer `PLACEHOLDER_MESSAGE_RESEND` request to its own account. If the phone still has the message it re-delivers it, and the recovered content arrives **asynchronously** as a normal `Message` event through the webhook and transports.

        **Rules:**
        - `MessageID` and `ChatJID` are required; keys are case-insensitive, so `messageId`, `chatJid`, `senderJid`, `retryType` and `forceRetry` also work.
        - `SenderJID` (the participant in groups) is required unless `ForceRetry` is `true`; without either the request fails with **500**.
        - `RetryType` is `incoming` (default) or `auto`, which behave the same. Any other value returns **500**.
        - The response only confirms that the resend request was sent. It never contains the message, does not guarantee recovery, and no cache lookup is performed.
        - Received view-once media cannot be recovered this way: WhatsApp does not deliver it to linked devices.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatRetryMessageRequest"
            examples:
              group_message:
                summary: Recover a group message
                description: The author of the group message is sent in `SenderJID`.
                value:
                  MessageID: 3EB0C767D26A1D6B8A9F
                  ChatJID: 120363000000000011@g.us
                  SenderJID: 5511888888888@s.whatsapp.net
              direct_forced:
                summary: Recover a direct message without sender
                description: "`ForceRetry` sends the request even though `SenderJID` is omitted."
                value:
                  MessageID: 3EB0C767D26A1D6B8A9F
                  ChatJID: "5511999999999"
                  ForceRetry: true
              camel_case_keys:
                summary: Same request with camelCase keys
                description: Keys are matched case-insensitively.
                value:
                  messageId: 3EB0C767D26A1D6B8A9F
                  chatJid: 120363000000000011@g.us
                  senderJid: 5511888888888@s.whatsapp.net
                  retryType: incoming
      responses:
        "200":
          description: Resend request sent to the primary phone; the content arrives later as a `Message` event.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatRetryMessageResponse"
              example:
                code: 200
                data:
                  Details: Message retry operation completed
                  Found: true
                  Sources:
                    - retry_request
                  RetryAttempts: 1
                  RetryType: incoming
                  CacheStatus: retry_initiated
                  ProcessedAt: "2026-09-15T10:30:00-03:00"
                  MessageData:
                    retryMethod: unavailable_message_request
                    requestedAt: "2026-09-15T10:30:00-03:00"
                    targetChat: 120363000000000011@g.us
                    targetSender: 5511888888888@s.whatsapp.net
                success: true
        "400":
          description: "Bad Request: the body is not valid JSON, `MessageID` or `ChatJID` is missing, or a JID cannot be parsed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                missing_message_id:
                  summary: MessageID missing
                  value:
                    code: 400
                    error: missing messageId in Payload
                    success: false
                missing_chat_jid:
                  summary: ChatJID missing
                  value:
                    code: 400
                    error: missing chatJid in Payload
                    success: false
                invalid_chat_jid:
                  summary: ChatJID cannot be parsed
                  value:
                    code: 400
                    error: invalid chatJid format
                    success: false
                invalid_sender_jid:
                  summary: SenderJID cannot be parsed
                  value:
                    code: 400
                    error: invalid senderJid format
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, `SenderJID` missing without `ForceRetry`, unsupported `RetryType`, or the resend request could not be sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                sender_required:
                  summary: SenderJID missing and ForceRetry false
                  value:
                    code: 500
                    error: senderJid is required for incoming message retry
                    success: false
                unsupported_retry_type:
                  summary: RetryType is not incoming or auto
                  value:
                    code: 500
                    error: unsupported retry type for this operation
                    success: false
                send_failed:
                  summary: Resend request could not be sent
                  value:
                    code: 500
                    error: "failed to send retry request: websocket not connected"
                    success: false
  /newsletter/{newsletterID}:
    delete:
      tags:
        - Newsletter
      summary: Permanently delete an owned channel
      description: |
        Permanently deletes a channel owned by this account. The deletion cannot be undone: the channel, its posts and
        its followers are removed on WhatsApp.

        **Rules:**
        - `newsletterID` is the full channel JID (`<digits>@newsletter`, numeric user part, no device), not only the digits.
        - The WhatsApp client must be connected (**500** `client not connected` otherwise).
        - Ownership is enforced by WhatsApp, not by the gateway: deleting a channel this account does not own, or a failed deletion, returns **502** `newsletter deletion was not confirmed`.
        - Do not retry automatically after a 502: check `GET /newsletter/subscribed` first.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: newsletterID
          in: path
          required: true
          description: Full JID of the channel to delete (`<digits>@newsletter`, no device).
          schema:
            type: string
          example: 120363000000000022@newsletter
      responses:
        "200":
          description: WhatsApp confirmed the deletion.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterDeleteResponse"
              example:
                code: 200
                data:
                  success: true
                  newsletter_jid: 120363000000000022@newsletter
                success: true
        "400":
          description: "Bad Request: the path value is not a non-device `@newsletter` JID with a numeric user part."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_newsletter_jid:
                  summary: Not an @newsletter JID
                  value:
                    code: 400
                    error: JID must be a non-device @newsletter identifier
                    success: false
                non_numeric_user:
                  summary: User part is not numeric
                  value:
                    code: 400
                    error: JID user must be numeric
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp did not confirm the deletion (for example, the account is not the channel owner)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: newsletter deletion was not confirmed
                success: false
  /user/contact-link/resolve:
    post:
      tags:
        - User
      summary: Resolve a WhatsApp contact QR link
      description: |
        Resolves a WhatsApp contact QR link (the "share my contact" QR code) to the account it points to. The gateway never downloads the URL: it only extracts the code and asks WhatsApp who owns it.

        **Rules:**
        - The body must be a single JSON object of at most 4096 bytes. Unknown fields or a second JSON value are rejected with **400**.
        - `code` is required: 1-2048 bytes, no whitespace or control characters. Send either the bare code or a full URL starting with `https://wa.me/qr/` or `https://api.whatsapp.com/qr/`.
        - Any other URL returns `unsupported contact QR URL`; a supported URL with a query string, fragment or empty code returns `invalid contact QR URL`.
        - The WhatsApp client must exist and be connected (**500** otherwise).
        - A revoked or unknown code returns **404**; any other WhatsApp failure returns **502**.

        **Response:**
        - The result is nested twice: `data.success` and `data.data` (`JID`, `Type`, `PushName`), inside the gateway envelope.

        **Use Cases:**
        - Turn a scanned contact QR code into a JID before sending the first message
        - Validate QR links collected in a CRM form
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        description: Contact QR code or supported WhatsApp QR URL.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.UserContactLinkResolveRequest"
            examples:
              wa_me_url:
                summary: Full wa.me QR URL
                description: The URL encoded in the QR code shown by "Share my contact" in the WhatsApp app.
                value:
                  code: https://wa.me/qr/ABCDEFGHIJKL12
              api_url:
                summary: api.whatsapp.com QR URL
                description: The alternative host is accepted too; the gateway only extracts the code after `/qr/`.
                value:
                  code: https://api.whatsapp.com/qr/ABCDEFGHIJKL12
              bare_code:
                summary: Bare code
                description: Only the code part of the QR link, without any URL prefix.
                value:
                  code: ABCDEFGHIJKL12
      responses:
        "200":
          description: Account behind the contact QR link.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserContactLinkResolveResponse"
              example:
                code: 200
                data:
                  success: true
                  data:
                    JID: 5511999999999@s.whatsapp.net
                    Type: contact
                    PushName: Example Store
                success: true
        "400":
          description: "Bad Request: the body is not one JSON object within 4096 bytes, or `code` is empty, contains whitespace or uses an unsupported/invalid URL."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not a JSON object, has unknown fields or exceeds 4096 bytes
                  value:
                    code: 400
                    error: invalid JSON object (maximum 4096 bytes)
                    success: false
                trailing_data:
                  summary: More than one JSON value in the body
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                bad_code:
                  summary: code missing, empty or containing whitespace
                  value:
                    code: 400
                    error: code must contain 1-2048 bytes without whitespace
                    success: false
                unsupported_url:
                  summary: URL from another host
                  value:
                    code: 400
                    error: unsupported contact QR URL
                    success: false
                invalid_url:
                  summary: Supported URL with query string, fragment or no code
                  value:
                    code: 400
                    error: invalid contact QR URL
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "404":
          description: "Not Found: WhatsApp does not know this code (revoked or never issued)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 404
                error: contact link not found
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Session exists but is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp failed to resolve the code, or returned no result."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: contact link resolution failed
                success: false
  /user/identity-code/{jid}:
    get:
      tags:
        - User
      summary: Get identity verification codes for a LID
      description: |
        Generates the public identity verification ("security code") values between this account and a contact: the same 60-digit number and QR payloads shown in the WhatsApp "Verify security code" screen.

        **Rules:**
        - `jid` must be a non-device `@lid` JID whose user part is numeric; phone JIDs, device JIDs and group JIDs return **400**.
        - The WhatsApp client must exist and be connected (**500** otherwise).
        - The contact's identity keys are fetched from WhatsApp; a failure or an empty answer returns **502**.

        **Response:**
        - The result is nested twice: `data.success` and `data.data`, inside the gateway envelope.
        - `DisplayQRCode` and `VerificationQRCode` are byte arrays encoded as base64 strings.
        - `PhoneNumber` and `Username` are empty when unknown. No private identity keys are returned.

        **Use Cases:**
        - Show the security code to an operator who verifies a contact in person
        - Detect identity changes by comparing codes over time
      security:
        - ApiKeyAuth: []
      parameters:
        - name: jid
          in: path
          required: true
          description: Contact LID without device suffix, URL-encoded if your client requires it (`@` may be sent as `%40`).
          schema:
            type: string
          example: 123456789012345@lid
      responses:
        "200":
          description: Identity verification codes for the contact.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserIdentityCodesResponse"
              example:
                code: 200
                data:
                  success: true
                  data:
                    UserID: 123456789012345@lid
                    PhoneNumber: 5511999999999@s.whatsapp.net
                    Username: ""
                    NumericCode: "123456789012345678901234567890123456789012345678901234567890"
                    DisplayQRCode: AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJw==
                    VerificationQRCode: KCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OTw==
                success: true
        "400":
          description: "Bad Request: `jid` is not a non-device `@lid` JID with a numeric user part."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                not_lid:
                  summary: Phone JID, device JID or other server
                  value:
                    code: 400
                    error: JID must be a non-device @lid identifier
                    success: false
                non_numeric:
                  summary: LID with a non-numeric user part
                  value:
                    code: 400
                    error: JID user must be numeric
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Session exists but is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: "Bad Gateway: WhatsApp did not return the contact's identity keys."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unavailable:
                  summary: Identity key query failed
                  value:
                    code: 502
                    error: identity verification codes unavailable
                    success: false
                empty:
                  summary: Query succeeded without codes
                  value:
                    code: 502
                    error: identity verification returned no codes
                    success: false
  /user/username/resolve:
    post:
      tags:
        - User
      summary: Resolve a WhatsApp username
      description: |
        Resolves a public WhatsApp username (for example `@examplestore`) to the owner's LID, so you can message an account known only by its username.

        **Rules:**
        - The body must be a single JSON object of at most 4096 bytes. Unknown fields or a second JSON value are rejected with **400**.
        - `username` is required: 3-35 characters after trimming surrounding spaces and one leading `@`, with no whitespace or control characters inside.
        - `key` is optional and must be exactly four digits. Send it only when the owner protects the username with a key.
        - The WhatsApp client must exist and be connected (**500** otherwise). A failed WhatsApp query returns **502**.

        **Behavior:**
        - When the username needs a key and none (or a wrong one) was sent, the call still returns **200** with `KeyRequired: true` and empty `LID`/`Username`.
        - On success the username is saved in the local contact store for that LID.
        - The result is nested twice: `data.success` and `data.data`, inside the gateway envelope.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        description: Username to resolve and, when needed, its four-digit key.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.UserUsernameResolveRequest"
            examples:
              plain:
                summary: Username without key
                description: Most usernames resolve without a key.
                value:
                  username: examplestore
              with_key:
                summary: Username protected by a key
                description: A leading `@` is accepted; `key` is the four-digit code the owner shared.
                value:
                  username: "@examplestore"
                  key: "4821"
      responses:
        "200":
          description: Resolution result. Check `data.data.KeyRequired` before using `LID`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserUsernameResolveResponse"
              examples:
                resolved:
                  summary: Username resolved
                  value:
                    code: 200
                    data:
                      success: true
                      data:
                        LID: 123456789012345@lid
                        Username: examplestore
                        KeyRequired: false
                    success: true
                key_required:
                  summary: Key missing or wrong
                  value:
                    code: 200
                    data:
                      success: true
                      data:
                        LID: ""
                        Username: ""
                        KeyRequired: true
                    success: true
        "400":
          description: "Bad Request: the body is not one JSON object within 4096 bytes, or `username`/`key` fail validation."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_json:
                  summary: Body is not a JSON object, has unknown fields or exceeds 4096 bytes
                  value:
                    code: 400
                    error: invalid JSON object (maximum 4096 bytes)
                    success: false
                trailing_data:
                  summary: More than one JSON value in the body
                  value:
                    code: 400
                    error: exactly one JSON object is required
                    success: false
                length:
                  summary: username missing, shorter than 3 or longer than 35 characters
                  value:
                    code: 400
                    error: username must contain 3-35 characters
                    success: false
                whitespace:
                  summary: username with inner whitespace
                  value:
                    code: 400
                    error: username contains whitespace or control characters
                    success: false
                key:
                  summary: key is not four digits
                  value:
                    code: 400
                    error: key must contain four digits
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session for this user, or the client is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Session exists but is not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: "Bad Gateway: the WhatsApp username query failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 502
                error: username resolution failed
                success: false
  /group/historysharemode:
    post:
      tags:
        - Group
      summary: Set who can share recent group history
      description: |
        Changes who may share recent message history with new group members: admins only (`admin_share`) or every member
        (`all_member_share`). The gateway writes the `member_share_group_history_mode` group property.

        **Rules:**
        - Changes the permission only. It does **not** send old messages and does not add participants; use `POST /group/history/send` to share retained history.
        - The body must be exactly one JSON object; trailing JSON returns **400**.
        - `mode` is validated before `groupjid`. `groupjid` must be a group JID (`@g.us`) without a device part.
        - WhatsApp decides whether this session may change the setting; any library or server failure returns the generic **500** `failed to set group history share mode`.
        - The write goes through WhatsApp's group-property update, the same path the official client uses, and answers in well
          under a second (measured live: 0.47 s for both modes). The older group IQ for this property is silently ignored by the
          server, which is why this route used to hang for the full IQ timeout before failing; reading the setting is unchanged.
        - A group that WhatsApp reports in a non-active state fails with the generic **500** instead of reporting success.

        **Use Cases:**
        - Allow any member to bring newcomers up to date in a project group
        - Restrict history sharing to admins in groups with sensitive content
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        description: Group JID and history sharing mode.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.GroupHistoryShareModeRequest"
            examples:
              admins_only:
                summary: Only admins can share history
                description: Restricts recent-history sharing to group admins.
                value:
                  groupjid: "120363000000000011@g.us"
                  mode: admin_share
              all_members:
                summary: All members can share history
                description: Lets every participant share recent history with new members.
                value:
                  groupjid: "120363000000000011@g.us"
                  mode: all_member_share
      responses:
        "200":
          description: WhatsApp accepted the new history share mode.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupDetailsResponse"
              example:
                code: 200
                data:
                  Details: Group history share mode set successfully
                success: true
        "400":
          description: "Bad Request: invalid JSON, more than one JSON value, unaccepted `mode`, or `groupjid` that is not a group JID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                could_not_decode_payload:
                  summary: Body is not valid JSON
                  value:
                    code: 400
                    error: could not decode Payload
                    success: false
                expected_single_payload:
                  summary: Trailing JSON after the object
                  value:
                    code: 400
                    error: expected a single JSON payload
                    success: false
                invalid_mode:
                  summary: mode is missing or not accepted
                  value:
                    code: 400
                    error: invalid mode, must be 'admin_share' or 'all_member_share'
                    success: false
                not_a_group_jid:
                  summary: groupjid is not an @g.us JID
                  value:
                    code: 400
                    error: groupjid must be a group JID
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, or the library/WhatsApp rejected the change."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                change_failed:
                  summary: Change rejected
                  value:
                    code: 500
                    error: failed to set group history share mode
                    success: false
  /business/catalog/own:
    get:
      tags:
        - Business
      summary: Get the own catalog, including hidden products
      description: |
        Reads the administrative catalog of the paired business account through the authenticated Meta management query, including hidden products and products still under review.

        **Features:**
        - Catalog ID, type and name plus one page of products with review status (`status_info`) and visibility (`is_hidden`)
        - Pagination with `limit` and `cursor`; pass `next` as the next `cursor`

        **Rules:**
        - `limit` 1-100 (default 20) and `cursor` (at most 2048 bytes) are validated before the session check (`400`)
        - Prices are strings with integer thousandths of the currency (`"3000000"` = 3,000.00)
        - Does not create or modify products, does not retry and does not use the catalog cache
        - Token exchange or query failures return `502 own business catalog unavailable: <reason>`; token acquisition may require the primary phone to be online

        **Use Cases:**
        - Read a product completely before `PUT /business/products/{productID}`
        - Track products pending Meta review
      parameters:
        - name: limit
          in: query
          required: false
          description: Page size, 1-100 (default 20). Must appear at most once.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          example: 20
        - name: cursor
          in: query
          required: false
          description: Opaque cursor copied from `next` of the previous page. At most once and 2048 bytes.
          schema:
            type: string
            maxLength: 2048
          example: AQHRcursorEXAMPLE
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Own catalog page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Typed business result wrapper.
                        required:
                          - success
                          - data
                        properties:
                          success:
                            type: boolean
                            description: Always true when the operation succeeded.
                            example: true
                          data:
                            type: object
                            description: Own catalog page.
                            properties:
                              catalog_id:
                                type: string
                                description: Catalog ID.
                                example: "7123456789012300"
                              catalog_type:
                                type: string
                                description: Catalog type reported by Meta.
                                example: NATIVE
                              catalog_name:
                                type: string
                                description: Catalog name.
                                example: Example Store
                              products:
                                type: array
                                description: Products on this page, including hidden and pending ones.
                                items:
                                  $ref: "#/components/schemas/BusinessCatalogProduct"
                              next:
                                type: string
                                description: Cursor for the next page; omitted on the last page.
                                example: AQHRcursorEXAMPLE
                              previous:
                                type: string
                                description: Cursor for the previous page, when present.
                                example: AQHRcursorPREVIOUS
              examples:
                first_page:
                  summary: First page with one product
                  value:
                    code: 200
                    data:
                      data:
                        catalog_id: "7123456789012300"
                        catalog_type: NATIVE
                        catalog_name: Example Store
                        products:
                          - id: "7123456789012345"
                            retailer_id: CHAIR-001
                            name: Wooden chair
                            description: Solid wood chair with natural finish.
                            price: "3000000"
                            currency: BRL
                            url: https://example.com/products/chair/
                            is_hidden: false
                            is_sanctioned: false
                            product_availability: in stock
                            compliance_info:
                              country_code_origin: BR
                            media:
                              images:
                                - id: "7123456789012346"
                                  original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                            sale_price:
                              price: "2499900"
                            status_info:
                              status: APPROVED
                        next: AQHRcursorEXAMPLE
                      success: true
                    success: true
        "400":
          description: "Invalid pagination: `limit must be between 1 and 100`; `limit must be provided once`; `cursor must be provided once and contain at most 2048 bytes`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                limit_out_of_range:
                  summary: limit out of range
                  value:
                    code: 400
                    error: limit must be between 1 and 100
                    success: false
                repeated_limit:
                  summary: Repeated limit
                  value:
                    code: 400
                    error: limit must be provided once
                    success: false
                repeated_or_oversized_cursor:
                  summary: Repeated or oversized cursor
                  value:
                    code: 400
                    error: cursor must be provided once and contain at most 2048 bytes
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "500":
          description: The user has no WhatsApp session or it is not connected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
        "502":
          description: The Meta token exchange or catalog query failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                primary_phone_offline:
                  summary: Primary phone offline
                  value:
                    code: 502
                    error: "own business catalog unavailable: business access token recovery is required on the primary device"
                    success: false
  /business/order/accept:
    post:
      tags:
        - Business
      summary: Accept a received order with a linked billing
      description: |
        Accepts an order received from a buyer by sending a billing message that quotes the original `OrderMessage`. No order enum is mutated.

        **Features:**
        - `billing.order_request_id` is derived from the verified incoming order
        - The header thumbnail is the order's own JPEG thumbnail (≤ 1 MiB); custom images are not allowed
        - The acknowledged billing is stored for later declarations (`X-Billing-Metadata-Status`)

        **Rules:**
        - `to`, `incoming_message_id` and `billing` are required; `body` and `id` are optional
        - `incoming_message_id` must be an `OrderMessage` received from this buyer and stored by this instance (`409 verified original message unavailable for this user, buyer and reference`)
        - Forbidden (`400`): `image`, `billing.order_request_id`, `billing_message_id`, `reference_id`, `status`, `order_status`, `payment_method`, `payment_timestamp`
        - `400 failed to attach verified original quote` when the quote cannot be built

        **Billing rules** (library validation, `400` with the builder error):
        - `reference_id` 1-256 bytes; `type` `physical-goods`; `currency` three uppercase letters; `order.order_type` `ORDER`; `order.status` `payment_requested`
        - 1-100 `order.items`, each with `name` (≤ 256), `quantity` 1-100, `product_id` or `retailer_id` and `amount`; custom items need `isCustomItem: true` and `retailer_id` starting with `custom-item` without `product_id`
        - Amounts are `{value, offset}` (`value/offset`); `order.subtotal` = Σ quantity × amount; `total_amount` = subtotal − discount + shipping + tax (discount ≤ subtotal)
        - At least one payment configuration: `payment_settings` (≤ 2, no repeated type) and/or `external_payment_configurations` (≤ 5)
      requestBody:
        required: true
        description: Buyer, received order message ID and explicit billing.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessBillingSendRequest"
            examples:
              accept:
                summary: Accept a received order
                description: Quotes the received order and requests payment with PIX.
                value:
                  to: 5511999999999@s.whatsapp.net
                  incoming_message_id: 3EB0C767D26A1D6B8A9F
                  body: Order accepted. Please review and pay.
                  billing:
                    reference_id: REF-0003
                    type: physical-goods
                    currency: BRL
                    total_amount:
                      value: 15000
                      offset: 100
                    order:
                      status: payment_requested
                      order_type: ORDER
                      items:
                        - product_id: "7123456789012345"
                          name: Wooden chair
                          quantity: 1
                          amount:
                            value: 15000
                            offset: 100
                      subtotal:
                        value: 15000
                        offset: 100
                    payment_settings:
                      - type: pix_static_code
                        pix_static_code:
                          key: payments@example.com
                          key_type: EMAIL
                          merchant_name: Example Store
              every_payment_method:
                summary: Accept offering PIX, card and external instructions
                description: >-
                  The three payment surfaces the protocol allows, in one billing: PIX static code, the native card option and free-text instructions (where a boleto, a checkout link or bank details go). At most two payment_settings, one per type; up to five external_payment_configurations. None of them moves money - the buyer pays outside the API and the seller declares the result with POST /business/order/payment/status/send.
                value:
                  to: 5511999999999@s.whatsapp.net
                  incoming_message_id: 3EB0C767D26A1D6B8A9F
                  body: "Order accepted. Pay with PIX, card or the link below."
                  billing:
                    reference_id: REF-0004
                    type: physical-goods
                    currency: BRL
                    total_amount:
                      value: 7500000
                      offset: 1000
                    order:
                      status: payment_requested
                      order_type: ORDER
                      items:
                        - product_id: "7123456789012345"
                          name: Automation setup
                          quantity: 1
                          amount:
                            value: 5000000
                            offset: 1000
                        - product_id: "7123456789012346"
                          name: WhatsApp API consulting
                          quantity: 1
                          amount:
                            value: 2500000
                            offset: 1000
                      subtotal:
                        value: 7500000
                        offset: 1000
                    payment_settings:
                      - type: pix_static_code
                        pix_static_code:
                          key: payments@example.com
                          key_type: EMAIL
                          merchant_name: Example Store
                      - type: cards
                        cards:
                          enabled: true
                    external_payment_configurations:
                      - type: payment_instruction
                        payment_instruction: "Boleto or hosted checkout: https://example.com/checkout/REF-0004"
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Message acknowledged
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Billing message acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          effect:
                            type: string
                            description: "Always `message_sent`: only a message was sent."
                            example: message_sent
                          financial_transaction_performed:
                            type: boolean
                            description: "Always false: no bank or payment transaction is performed."
                            example: false
              examples:
                sent:
                  summary: Billing message acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      effect: message_sent
                      financial_transaction_performed: false
                    success: true
          headers:
            X-Billing-Metadata-Status:
              description: "`stored` or `unavailable`. `unavailable` means later declarations cannot quote this billing; it does NOT mean the send failed, so do not resend."
              schema:
                type: string
                enum:
                  - stored
                  - unavailable
              example: stored
        "400":
          description: "Invalid request: `accept uses the verified incoming order thumbnail; image override is not allowed`; `failed to attach verified original quote`; `invalid billing request`; `only one JSON object is allowed`; `explicit individual buyer and valid message ID are required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                image_not_allowed:
                  summary: Image not allowed
                  value:
                    code: 400
                    error: accept uses the verified incoming order thumbnail; image override is not allowed
                    success: false
                quote_cannot_be_attached:
                  summary: Quote cannot be attached
                  value:
                    code: 400
                    error: failed to attach verified original quote
                    success: false
                malformed_body_or_unknown_field:
                  summary: Malformed body or unknown field
                  value:
                    code: 400
                    error: invalid billing request
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: only one JSON object is allowed
                    success: false
                invalid_buyer_or_message_id:
                  summary: Invalid buyer or message ID
                  value:
                    code: 400
                    error: explicit individual buyer and valid message ID are required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "403":
          description: Enterprise license required. The gateway returns this fixed message in Portuguese.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enterprise_license_missing:
                  summary: Enterprise license missing
                  value:
                    code: 403
                    error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                    success: false
        "409":
          description: The instance identity or the verified original message is not available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                own_device_identity_missing:
                  summary: Own device identity missing
                  value:
                    code: 409
                    error: own device identity unavailable
                    success: false
                incoming_order_not_stored:
                  summary: Incoming order not stored
                  value:
                    code: 409
                    error: verified original message unavailable for this user, buyer and reference
                    success: false
        "500":
          description: No session, client not connected, or the buyer PN/LID lookup failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                buyer_identity_lookup_failed:
                  summary: Buyer identity lookup failed
                  value:
                    code: 500
                    error: buyer identity lookup failed
                    success: false
        "502":
          description: WhatsApp did not acknowledge the message. Delivery is not confirmed; do not retry blindly because the buyer may have received it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                send_not_acknowledged:
                  summary: Send not acknowledged
                  value:
                    code: 502
                    error: billing message send failed; delivery is not confirmed
                    success: false
  /business/order/charge/send:
    post:
      tags:
        - Business
      summary: Send a billing message
      description: |
        Sends a standalone billing ("review and pay") message to a buyer: an interactive message with the `review_and_pay` button carrying the `billing` payload.

        **Features:**
        - PIX static code, cards and external payment instructions
        - Optional `image` (JPEG/PNG base64 or `data:` URI within the 256 KiB body limit) becomes a 72×72 JPEG header thumbnail
        - The acknowledged billing is stored so later status and payment declarations can quote it (`X-Billing-Metadata-Status`)

        **Rules:**
        - `to` and `billing` are required; `body` and `id` are optional
        - Forbidden in this mode (`400 invalid billing creation fields; order_request_id is derived from verified incoming order only`): `billing.order_request_id`, `incoming_message_id`, `billing_message_id`, `reference_id`, `status`, `order_status`, `payment_method`, `payment_timestamp`

        **Billing rules** (library validation, `400` with the builder error):
        - `reference_id` 1-256 bytes; `type` `physical-goods`; `currency` three uppercase letters; `order.order_type` `ORDER`; `order.status` `payment_requested`
        - 1-100 `order.items`, each with `name` (≤ 256), `quantity` 1-100, `product_id` or `retailer_id` and `amount`; custom items need `isCustomItem: true` and `retailer_id` starting with `custom-item` without `product_id`
        - Amounts are `{value, offset}` (`value/offset`); `order.subtotal` = Σ quantity × amount; `total_amount` = subtotal − discount + shipping + tax (discount ≤ subtotal)
        - At least one payment configuration: `payment_settings` (≤ 2, no repeated type) and/or `external_payment_configurations` (≤ 5)
      requestBody:
        required: true
        description: Buyer, optional text and explicit billing.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessBillingSendRequest"
            examples:
              pix:
                summary: Minimal charge with PIX
                description: One catalog item paid with a PIX static code.
                value:
                  to: 5511999999999@s.whatsapp.net
                  body: Your order is ready for payment.
                  billing:
                    reference_id: REF-0001
                    type: physical-goods
                    currency: BRL
                    total_amount:
                      value: 15000
                      offset: 100
                    order:
                      status: payment_requested
                      order_type: ORDER
                      items:
                        - product_id: "7123456789012345"
                          name: Wooden chair
                          quantity: 1
                          amount:
                            value: 15000
                            offset: 100
                      subtotal:
                        value: 15000
                        offset: 100
                    payment_settings:
                      - type: pix_static_code
                        pix_static_code:
                          key: payments@example.com
                          key_type: EMAIL
                          merchant_name: Example Store
              full:
                summary: Custom item, discount, shipping, cards and external link
                description: Two lines (one custom), discount and shipping, card payment and an external payment instruction.
                value:
                  to: 5511999999999@s.whatsapp.net
                  id: 3EB0C767D26A1D6B8A90
                  body: Your order is ready for payment.
                  billing:
                    reference_id: REF-0002
                    type: physical-goods
                    currency: BRL
                    total_amount:
                      value: 32000
                      offset: 100
                    order:
                      status: payment_requested
                      order_type: ORDER
                      items:
                        - product_id: "7123456789012345"
                          name: Wooden chair
                          quantity: 2
                          amount:
                            value: 15000
                            offset: 100
                        - isCustomItem: true
                          retailer_id: custom-item-assembly
                          name: Assembly service
                          quantity: 1
                          amount:
                            value: 3000
                            offset: 100
                      subtotal:
                        value: 33000
                        offset: 100
                      discount:
                        value: 3000
                        offset: 100
                      shipping:
                        value: 2000
                        offset: 100
                    additional_note: Delivery in 5 business days.
                    payment_settings:
                      - type: cards
                        cards:
                          enabled: true
                    external_payment_configurations:
                      - type: payment_instruction
                        payment_instruction: Pay at https://example.com/pay/REF-0002
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Message acknowledged
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Billing message acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          effect:
                            type: string
                            description: "Always `message_sent`: only a message was sent."
                            example: message_sent
                          financial_transaction_performed:
                            type: boolean
                            description: "Always false: no bank or payment transaction is performed."
                            example: false
              examples:
                sent:
                  summary: Billing message acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      effect: message_sent
                      financial_transaction_performed: false
                    success: true
          headers:
            X-Billing-Metadata-Status:
              description: "`stored` or `unavailable`. `unavailable` means later declarations cannot quote this billing; it does NOT mean the send failed, so do not resend."
              schema:
                type: string
                enum:
                  - stored
                  - unavailable
              example: stored
        "400":
          description: "Invalid request: `invalid billing creation fields; order_request_id is derived from verified incoming order only`; `billing total does not equal subtotal minus discount plus shipping and tax`; `invalid billing request`; `only one JSON object is allowed`; `explicit individual buyer and valid message ID are required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                forbidden_creation_fields:
                  summary: Forbidden creation fields
                  value:
                    code: 400
                    error: invalid billing creation fields; order_request_id is derived from verified incoming order only
                    success: false
                totals_do_not_match:
                  summary: Totals do not match
                  value:
                    code: 400
                    error: billing total does not equal subtotal minus discount plus shipping and tax
                    success: false
                malformed_body_or_unknown_field:
                  summary: Malformed body or unknown field
                  value:
                    code: 400
                    error: invalid billing request
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: only one JSON object is allowed
                    success: false
                invalid_buyer_or_message_id:
                  summary: Invalid buyer or message ID
                  value:
                    code: 400
                    error: explicit individual buyer and valid message ID are required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "403":
          description: Enterprise license required. The gateway returns this fixed message in Portuguese.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enterprise_license_missing:
                  summary: Enterprise license missing
                  value:
                    code: 403
                    error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                    success: false
        "409":
          description: The instance identity or the verified original message is not available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                own_device_identity_missing:
                  summary: Own device identity missing
                  value:
                    code: 409
                    error: own device identity unavailable
                    success: false
                quote_cannot_be_represented:
                  summary: Quote cannot be represented
                  value:
                    code: 409
                    error: verified billing quote cannot represent this declaration
                    success: false
        "500":
          description: No session, client not connected, or the buyer PN/LID lookup failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                buyer_identity_lookup_failed:
                  summary: Buyer identity lookup failed
                  value:
                    code: 500
                    error: buyer identity lookup failed
                    success: false
        "502":
          description: WhatsApp did not acknowledge the message. Delivery is not confirmed; do not retry blindly because the buyer may have received it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                send_not_acknowledged:
                  summary: Send not acknowledged
                  value:
                    code: 502
                    error: billing message send failed; delivery is not confirmed
                    success: false
  /business/order/payment/status/send:
    post:
      tags:
        - Business
      summary: Send a payment status declaration
      description: |
        Sends a payment status declaration (`payment_status` native flow) quoting a billing message previously sent by this instance. It does not move money and does not verify any payment.

        **Rules:**
        - `to`, `billing_message_id`, `reference_id` and `status` are required; `status` is `pending`, `captured` or `failed`
        - At least one of `payment_method` (≤ 64 bytes) or `payment_timestamp` (positive integer) is required
        - `order_status` is optional and uses the order lifecycle values (`pending`, `processing`, `partially_shipped`, `shipped`, `completed`, `canceled`, `payment_requested`, `preparing_to_ship`, `delivered`, `confirmed`, `delayed`, `out_for_delivery`, `failed`, `refunded`)
        - Invalid combinations return `400 invalid payment lifecycle claim`
        - Forbidden: `image`, `billing`, `incoming_message_id` (`400 own billing_message_id and reference_id are required`)
        - The billing must be stored for this user, buyer and reference and contain one `review_and_pay` button (`409`)
        - Like the order status route, the billing is quoted unchanged. The declaration reaches the buyer as its own message
          (a "payment received" bubble); it does not flip the "Paid" toggle on the billing card, which only the seller's app
          controls.
      requestBody:
        required: true
        description: Buyer, own billing message, reference and payment claim.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessBillingSendRequest"
            examples:
              captured:
                summary: Payment captured
                description: Declares a captured PIX payment and moves the order to processing.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: captured
                  payment_method: pix
                  payment_timestamp: 1757937600
                  order_status: processing
                  body: Payment received, thank you.
              method_only:
                summary: Pending with method only
                description: Declares a pending payment identified only by its method.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: pending
                  payment_method: pix
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Message acknowledged
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Billing message acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          effect:
                            type: string
                            description: "Always `message_sent`: only a message was sent."
                            example: message_sent
                          financial_transaction_performed:
                            type: boolean
                            description: "Always false: no bank or payment transaction is performed."
                            example: false
              examples:
                sent:
                  summary: Billing message acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      effect: message_sent
                      financial_transaction_performed: false
                    success: true
        "400":
          description: "Invalid request: `invalid payment lifecycle claim`; `own billing_message_id and reference_id are required`; `invalid billing request`; `only one JSON object is allowed`; `explicit individual buyer and valid message ID are required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_payment_claim:
                  summary: Invalid payment claim
                  value:
                    code: 400
                    error: invalid payment lifecycle claim
                    success: false
                billing_reference_missing:
                  summary: Billing reference missing
                  value:
                    code: 400
                    error: own billing_message_id and reference_id are required
                    success: false
                malformed_body_or_unknown_field:
                  summary: Malformed body or unknown field
                  value:
                    code: 400
                    error: invalid billing request
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: only one JSON object is allowed
                    success: false
                invalid_buyer_or_message_id:
                  summary: Invalid buyer or message ID
                  value:
                    code: 400
                    error: explicit individual buyer and valid message ID are required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "403":
          description: Enterprise license required. The gateway returns this fixed message in Portuguese.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enterprise_license_missing:
                  summary: Enterprise license missing
                  value:
                    code: 403
                    error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                    success: false
        "409":
          description: The instance identity or the verified original message is not available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                own_device_identity_missing:
                  summary: Own device identity missing
                  value:
                    code: 409
                    error: own device identity unavailable
                    success: false
                billing_not_stored_for_buyer_and_reference:
                  summary: Billing not stored for buyer and reference
                  value:
                    code: 409
                    error: verified original message unavailable for this user, buyer and reference
                    success: false
                quote_is_not_a_review_and_pay_billing:
                  summary: Quote is not a review_and_pay billing
                  value:
                    code: 409
                    error: verified billing quote cannot represent this declaration
                    success: false
        "500":
          description: No session, client not connected, or the buyer PN/LID lookup failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                buyer_identity_lookup_failed:
                  summary: Buyer identity lookup failed
                  value:
                    code: 500
                    error: buyer identity lookup failed
                    success: false
        "502":
          description: WhatsApp did not acknowledge the message. Delivery is not confirmed; do not retry blindly because the buyer may have received it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                send_not_acknowledged:
                  summary: Send not acknowledged
                  value:
                    code: 502
                    error: billing message send failed; delivery is not confirmed
                    success: false
  /business/order/status/send:
    post:
      tags:
        - Business
      summary: Send an order status declaration
      description: |
        Sends an order lifecycle declaration (`review_order` native flow) quoting a billing message previously sent by this instance, with the new status written into the quoted order.

        **Rules:**
        - `to`, `billing_message_id` (own billing message ID), `reference_id` (the `billing.reference_id` of that billing) and `status` are required; `body` and `id` are optional
        - `status` must be one of `pending`, `processing`, `partially_shipped`, `shipped`, `completed`, `canceled`, `payment_requested`, `preparing_to_ship`, `delivered`, `confirmed`, `delayed`, `out_for_delivery`, `failed`, `refunded` (`400 invalid order lifecycle claim`)
        - Forbidden: `image`, `billing`, `incoming_message_id` (`400 own billing_message_id and reference_id are required`) and `payment_method`, `payment_timestamp`, `order_status` (`400 payment fields are not order status fields`)
        - The billing must be stored for this user, buyer and reference and contain one `review_and_pay` button (`409`)
        - The declaration quotes the billing **exactly as it was sent**: the quoted payload is never rewritten, matching what the
          official client does.
        - **The order card does not follow these declarations.** Each one arrives as its own message ("Status: shipped", and so
          on), which is what the buyer reads; the badge on the billing card only moves through the seller's own app. The client
          reserves card updates for a button name (`order_status`) that the buyer's app does not render — measured live on
          2026-09-16, both ways. Treat the messages as the delivery channel, not the card.

        **Typical sequence** (validated end to end on a real order): `POST /business/order/accept` sends the billing, then this
        route walks the order through `confirmed` -> `processing` -> `preparing_to_ship` -> `shipped` -> `out_for_delivery` ->
        `delivered` -> `completed`. Each call is an independent declaration quoting the same billing: there is no server-side
        state machine here, so any order of statuses is accepted and the buyer sees every declaration. Payment is declared
        separately with `POST /business/order/payment/status/send`.
      requestBody:
        required: true
        description: Buyer, own billing message, reference and status.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessBillingSendRequest"
            examples:
              shipped:
                summary: Order shipped
                description: Declares that the order quoted by the billing was shipped.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: shipped
                  body: Your order is on its way.
              confirmed:
                summary: Order confirmed (first step after accepting)
                description: Opens the lifecycle right after the billing was sent.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: confirmed
                  body: Order confirmed. We are preparing it now.
              delivered:
                summary: Order delivered
                description: Declared when the buyer has the goods; `completed` usually follows.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: delivered
              completed:
                summary: Order completed (closes the lifecycle)
                description: Last declaration of the flow; nothing prevents sending further statuses afterwards.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: completed
              canceled:
                summary: Order canceled
                description: Declares the order as canceled without extra text.
                value:
                  to: 5511999999999@s.whatsapp.net
                  billing_message_id: 3EB0C767D26A1D6B8A9F
                  reference_id: REF-0001
                  status: canceled
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Message acknowledged
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Billing message acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          effect:
                            type: string
                            description: "Always `message_sent`: only a message was sent."
                            example: message_sent
                          financial_transaction_performed:
                            type: boolean
                            description: "Always false: no bank or payment transaction is performed."
                            example: false
              examples:
                sent:
                  summary: Billing message acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      effect: message_sent
                      financial_transaction_performed: false
                    success: true
        "400":
          description: "Invalid request: `invalid order lifecycle claim`; `own billing_message_id and reference_id are required`; `payment fields are not order status fields`; `invalid billing request`; `only one JSON object is allowed`; `explicit individual buyer and valid message ID are required`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                unknown_status:
                  summary: Unknown status
                  value:
                    code: 400
                    error: invalid order lifecycle claim
                    success: false
                billing_reference_missing:
                  summary: Billing reference missing
                  value:
                    code: 400
                    error: own billing_message_id and reference_id are required
                    success: false
                payment_fields_sent:
                  summary: Payment fields sent
                  value:
                    code: 400
                    error: payment fields are not order status fields
                    success: false
                malformed_body_or_unknown_field:
                  summary: Malformed body or unknown field
                  value:
                    code: 400
                    error: invalid billing request
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: only one JSON object is allowed
                    success: false
                invalid_buyer_or_message_id:
                  summary: Invalid buyer or message ID
                  value:
                    code: 400
                    error: explicit individual buyer and valid message ID are required
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "403":
          description: Enterprise license required. The gateway returns this fixed message in Portuguese.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enterprise_license_missing:
                  summary: Enterprise license missing
                  value:
                    code: 403
                    error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                    success: false
        "409":
          description: The instance identity or the verified original message is not available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                own_device_identity_missing:
                  summary: Own device identity missing
                  value:
                    code: 409
                    error: own device identity unavailable
                    success: false
                billing_not_stored_for_buyer_and_reference:
                  summary: Billing not stored for buyer and reference
                  value:
                    code: 409
                    error: verified original message unavailable for this user, buyer and reference
                    success: false
                quote_is_not_a_review_and_pay_billing:
                  summary: Quote is not a review_and_pay billing
                  value:
                    code: 409
                    error: verified billing quote cannot represent this declaration
                    success: false
        "500":
          description: No session, client not connected, or the buyer PN/LID lookup failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                buyer_identity_lookup_failed:
                  summary: Buyer identity lookup failed
                  value:
                    code: 500
                    error: buyer identity lookup failed
                    success: false
        "502":
          description: WhatsApp did not acknowledge the message. Delivery is not confirmed; do not retry blindly because the buyer may have received it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                send_not_acknowledged:
                  summary: Send not acknowledged
                  value:
                    code: 502
                    error: billing message send failed; delivery is not confirmed
                    success: false
  /business/order/reject:
    post:
      tags:
        - Business
      summary: Send an order refusal message
      description: |
        Refuses a received order by replying to the original `OrderMessage` with WhatsApp's official refusal text plus an optional note.

        **Features:**
        - The sent text is the fixed Portuguese sentence `Este pedido foi recusado.` (as written by the official app), followed by a line break and `body` when present
        - Quotes the verified incoming order

        **Rules:**
        - Requires an Enterprise license (`403`, checked first)
        - Body: exactly one JSON object, unknown fields rejected, maximum 16 KiB (`400 invalid order rejection request` / `only one JSON object is allowed`)
        - `to` must be an individual buyer JID (`@s.whatsapp.net` or `@lid`, no device); `incoming_message_id` 1-128 bytes; `id` ≤ 128 bytes; `body` ≤ 4096 bytes
        - The incoming order must be stored for this user and buyer (`409 verified incoming order unavailable for this user and buyer`)
        - Does not mutate an order enum and does not verify the remote order state (`remote_order_state_verified: false`)
      requestBody:
        required: true
        description: Buyer, received order message ID and optional note.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.BusinessOrderRejectRequest"
            examples:
              minimal:
                summary: Refuse without note
                description: Sends only the official refusal sentence.
                value:
                  to: 5511999999999@s.whatsapp.net
                  incoming_message_id: 3EB0C767D26A1D6B8A9F
              with_note:
                summary: Refuse with note and custom message ID
                description: Adds a note on the next line and sets the outgoing message ID.
                value:
                  to: 5511999999999@s.whatsapp.net
                  incoming_message_id: 3EB0C767D26A1D6B8A9F
                  id: 3EB0C767D26A1D6B8A90
                  body: Item out of stock.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Refusal message acknowledged
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Refusal acknowledgement.
                        properties:
                          success:
                            type: boolean
                            description: Always true when WhatsApp acknowledged the message.
                            example: true
                          message_id:
                            type: string
                            description: WhatsApp message ID of the sent message.
                            example: 3EB0C767D26A1D6B8A9F
                          timestamp:
                            type: string
                            description: Server acknowledgement time (RFC 3339).
                            format: date-time
                            example: "2026-09-15T12:00:00Z"
                          to:
                            type: string
                            description: Recipient JID the message was sent to.
                            example: 5511999999999@s.whatsapp.net
                          effect:
                            type: string
                            description: Always `rejection_message_sent`.
                            example: rejection_message_sent
                          remote_order_state_verified:
                            type: boolean
                            description: "Always false: the remote order state is not checked."
                            example: false
              examples:
                sent:
                  summary: Refusal acknowledged
                  value:
                    code: 200
                    data:
                      success: true
                      message_id: 3EB0C767D26A1D6B8A9F
                      timestamp: "2026-09-15T12:00:00Z"
                      to: 5511999999999@s.whatsapp.net
                      effect: rejection_message_sent
                      remote_order_state_verified: false
                    success: true
        "400":
          description: "Invalid request: `invalid order rejection request`; `only one JSON object is allowed`; `individual buyer, incoming_message_id and bounded rejection note are required`; `failed to attach verified original quote`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                malformed_body_or_unknown_field:
                  summary: Malformed body or unknown field
                  value:
                    code: 400
                    error: invalid order rejection request
                    success: false
                more_than_one_json_value:
                  summary: More than one JSON value
                  value:
                    code: 400
                    error: only one JSON object is allowed
                    success: false
                invalid_buyer_message_id_or_note:
                  summary: Invalid buyer, message ID or note
                  value:
                    code: 400
                    error: individual buyer, incoming_message_id and bounded rejection note are required
                    success: false
                quote_cannot_be_attached:
                  summary: Quote cannot be attached
                  value:
                    code: 400
                    error: failed to attach verified original quote
                    success: false
        "401":
          description: Missing or invalid user token (`token` header).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_token:
                  summary: Invalid token
                  value:
                    code: 401
                    error: unauthorized
                    success: false
        "403":
          description: Enterprise license required. The gateway returns this fixed message in Portuguese.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                enterprise_license_missing:
                  summary: Enterprise license missing
                  value:
                    code: 403
                    error: este recurso requer licença Enterprise; entre em contato com o suporte para adquirir acesso
                    success: false
        "409":
          description: The instance identity or the verified incoming order is not available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                own_device_identity_missing:
                  summary: Own device identity missing
                  value:
                    code: 409
                    error: own device identity unavailable
                    success: false
                incoming_order_not_stored:
                  summary: Incoming order not stored
                  value:
                    code: 409
                    error: verified incoming order unavailable for this user and buyer
                    success: false
        "500":
          description: No session, client not connected, or the buyer PN/LID lookup failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No session
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: Client not connected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                buyer_identity_lookup_failed:
                  summary: Buyer identity lookup failed
                  value:
                    code: 500
                    error: buyer identity lookup failed
                    success: false
        "502":
          description: WhatsApp did not acknowledge the message. Delivery is not confirmed; do not retry blindly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                send_not_acknowledged:
                  summary: Send not acknowledged
                  value:
                    code: 502
                    error: rejection message send failed; delivery is not confirmed
                    success: false
  /group/history/send:
    post:
      tags:
        - Group
      summary: Send retained group history to selected members
      description: |
        Shares group messages that this gateway retained for the user with selected current members, as a recent-history
        bundle. The sequence mirrors the official linked client: upload the history blob, send a `MessageHistoryBundle`
        message (`bundle_id`), then a `MessageHistoryNotice` message (`notice_id`). It never adds members and never retries.

        **Rules:**
        - Body: one JSON object, at most 32 KiB, unknown fields rejected. All five fields are required.
        - `group_jid`: `@g.us` JID without device. `message_ids`: 1-100 distinct IDs (max 128 chars) of group messages retained for this user and group.
        - `recipients`: 1-100 distinct phone numbers or user JIDs (`@s.whatsapp.net` / `@lid`, no device). Each must be another current member; the session itself is not a valid recipient.
        - `bundle_id` and `notice_id`: non-empty, max 128 chars, different from each other, never used before.
        - Only primary content is retained (text, media, contacts, locations, polls, events, stickers, invites); reactions, votes, edits and view-once content are not.
        - Retention is bounded: originals are kept for `GLOBAL_GROUP_HISTORY_RETENTION_DAYS` (default 90, which is also the maximum; `0` disables retention) and at most `GLOBAL_GROUP_HISTORY_RETENTION_MAX_PER_USER` (default 100000) newest originals per user. A message outside the window answers the same **409** as a message that was never retained; retained messages behave exactly as before.

        **Side Effects:**
        - Both IDs are durably reserved **before** any network call. Reusing either returns **409**, even when the earlier attempt failed.
        - Admin membership and recipient membership are checked by the library after the reservation, so a failure there also consumes the IDs (**502**, `phase: validation`).

        **Delivery:**
        - `bundle_acknowledged` / `notice_acknowledged` are server acknowledgements only. `recipient_delivery_verified` is always `false` and `retry_safe` is always `false`.
        - **502** keeps the legacy envelope: top-level `success` is `true`; inspect `data.success`, `data.result.phase` and `data.error`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        description: Group, retained message IDs, recipients and two unused operation IDs.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/main.GroupHistorySendRequest"
            examples:
              one_recipient:
                summary: Share two retained messages with one member
                description: Recipient given as a phone number; both operation IDs are new.
                value:
                  group_jid: "120363000000000011@g.us"
                  message_ids:
                    - 3EB0C767D26A1D6B8A9F
                    - 3EB0C767D26A1D6B8A9E
                  recipients:
                    - "5511988888888"
                  bundle_id: 3EB0C767D26A1D6B8AA0
                  notice_id: 3EB0C767D26A1D6B8AA1
              jid_recipients:
                summary: Recipients as user JIDs
                description: Phone JID and LID recipients may be mixed; each must resolve to a different current member.
                value:
                  group_jid: "120363000000000011@g.us"
                  message_ids:
                    - 3EB0C767D26A1D6B8A9F
                  recipients:
                    - 5511988888888@s.whatsapp.net
                    - 123456789012345@lid
                  bundle_id: 3EB0C767D26A1D6B8AB0
                  notice_id: 3EB0C767D26A1D6B8AB1
      responses:
        "200":
          description: Bundle and notice were both acknowledged by the WhatsApp server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupHistorySendResponse"
              example:
                code: 200
                data:
                  success: true
                  result:
                    bundle_id: 3EB0C767D26A1D6B8AA0
                    notice_id: 3EB0C767D26A1D6B8AA1
                    phase: acknowledged
                    bundle_acknowledged: true
                    notice_acknowledged: true
                  retry_safe: false
                  recipient_delivery_verified: false
                  evidence_persisted: true
                success: true
        "400":
          description: "Bad Request: invalid or oversized JSON, unknown fields, trailing JSON, invalid group/IDs, invalid or duplicate recipient or message ID, or a history window over 16 MiB."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                invalid_history_request:
                  summary: Invalid JSON, unknown field or body over 32 KiB
                  value:
                    code: 400
                    error: invalid history request
                    success: false
                only_one_json_object:
                  summary: Trailing JSON after the object
                  value:
                    code: 400
                    error: only one JSON object allowed
                    success: false
                invalid_group_or_ids:
                  summary: Missing or invalid group, list sizes or operation IDs
                  value:
                    code: 400
                    error: invalid group, messages, recipients or operation IDs
                    success: false
                invalid_recipient:
                  summary: Recipient is not a user JID or is repeated
                  value:
                    code: 400
                    error: invalid or duplicate history recipient
                    success: false
                invalid_message_id:
                  summary: Message ID empty, too long or repeated
                  value:
                    code: 400
                    error: invalid or duplicate original message ID
                    success: false
                window_too_large:
                  summary: Retained history exceeds the size limit
                  value:
                    code: 400
                    error: history window exceeds limits
                    success: false
        "401":
          description: Missing or invalid user token (header `token` or query `token`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              example:
                code: 401
                error: unauthorized
                success: false
        "409":
          description: "Conflict: a requested message is not retained for this user and group, or `bundle_id`/`notice_id` was already reserved. Nothing was sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                original_unavailable:
                  summary: Message not retained
                  value:
                    code: 409
                    error: original history unavailable for this user and group
                    success: false
                ids_already_used:
                  summary: bundle_id or notice_id already reserved
                  value:
                    code: 409
                    error: history IDs already used or reservation unavailable; send not attempted
                    success: false
        "500":
          description: "Internal Server Error: no WhatsApp session, client not connected, or the result could not be serialized."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
              examples:
                no_session:
                  summary: No WhatsApp session for this user
                  value:
                    code: 500
                    error: no session
                    success: false
                client_not_connected:
                  summary: WhatsApp client disconnected
                  value:
                    code: 500
                    error: client not connected
                    success: false
                serialization_failed:
                  summary: Result could not be encoded
                  value:
                    code: 500
                    error: history result serialization failed
                    success: false
        "502":
          description: "Bad Gateway: the history send failed or stayed unconfirmed after the IDs were reserved. Legacy envelope: top-level `success` is `true`; `data.success` is `false` and `data.result.phase` names the failing step (`validation`, `upload`, `bundle` or `notice`). Do not retry with the same IDs."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupHistorySendFailureResponse"
              examples:
                notice_failed:
                  summary: Bundle acknowledged, notice failed
                  value:
                    code: 502
                    data:
                      success: false
                      error: history operation failed or remains unconfirmed; do not retry reserved IDs
                      result:
                        bundle_id: 3EB0C767D26A1D6B8AA0
                        notice_id: 3EB0C767D26A1D6B8AA1
                        phase: notice
                        bundle_acknowledged: true
                        notice_acknowledged: false
                      retry_safe: false
                      recipient_delivery_verified: false
                      evidence_persisted: true
                    success: true
                not_admin:
                  summary: Session is not a group admin
                  value:
                    code: 502
                    data:
                      success: false
                      error: history operation failed or remains unconfirmed; do not retry reserved IDs
                      result:
                        bundle_id: 3EB0C767D26A1D6B8AB0
                        notice_id: 3EB0C767D26A1D6B8AB1
                        phase: validation
                        bundle_acknowledged: false
                        notice_acknowledged: false
                      retry_safe: false
                      recipient_delivery_verified: false
                      evidence_persisted: true
                    success: true

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: token
    AdminAuth:
      type: apiKey
      in: header
      name: Authorization
  schemas:
    ContextInfo:
      type: object
      description: Optional context info for replies. When replying to a message, provide stanzaID, participant, and optionally quotedMessage for full reply preview support on all devices.
      properties:
        stanzaID:
          type: string
          example: 3EB06F9067F80BAB89FF
          description: Message ID to reply to (required for replies)
        participant:
          type: string
          example: 5511999999999@s.whatsapp.net
          description: JID of original message sender (required for replies)
        quotedMessage:
          $ref: "#/components/schemas/QuotedMessage"
        isForwarded:
          type: boolean
          example: true
          description: Set to true to mark the message as forwarded. This is the preferred method for forwarding messages.
        mentionedJID:
          type: array
          items:
            type: string
          example:
            - 5511999999999@s.whatsapp.net
            - 5491155553934@s.whatsapp.net
          description: Array of JIDs to mention in the message. Use full WhatsApp JID format (number@s.whatsapp.net).
        mentionAll:
          type: boolean
          example: true
          description: Set to true to mention all group members
    QuotedMessage:
      type: object
      description: The original message content being replied to. Providing this ensures proper reply preview display on all devices, especially iPhone. Without this field, replies will work but may not show the original message preview correctly on iOS devices.
      properties:
        conversation:
          type: string
          example: Hello, how are you?
          description: The text content of the original message being replied to. Use this for simple text messages.
      example:
        conversation: This is the original message text being replied to
    DLQEvent:
      type: object
      description: Event that exhausted its delivery attempts on a transport (archive row with status `dlq`).
      properties:
        id:
          type: integer
          format: int64
          description: Archive row ID.
          example: 1234
        transport:
          type: string
          description: Transport that failed the delivery.
          example: global-webhook
        user_id:
          type: string
          description: User ID of the event.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Gateway event type.
          example: Message
        payload:
          type: string
          description: Original event envelope as a JSON string (includes `userToken`).
          example: '{"id":"evt_01J8Z6","eventType":"Message","payload":{"type":"Message"}}'
        attempts:
          type: integer
          description: Delivery attempts made before the event was moved to the DLQ.
          example: 12
        last_error:
          type: string
          description: Error of the last delivery attempt.
          example: webhook returned status 503
        failed_at:
          type: string
          format: date-time
          description: Time of the final failure; omitted when not recorded.
          example: "2026-09-15T10:30:00Z"
        archived_at:
          type: string
          format: date-time
          description: Time the row was written to the archive.
          example: "2026-09-15T10:30:00Z"
        replay_count:
          type: integer
          description: Times the event was replayed.
          example: 0
        last_replay_at:
          type: string
          format: date-time
          description: Time of the last replay; omitted when never replayed.
          example: "2026-09-15T11:00:00Z"
    DLQStats:
      type: object
      description: Dead Letter Queue statistics.
      properties:
        total_count:
          type: integer
          format: int64
          description: Events in the DLQ.
          example: 57
        oldest_event:
          type: string
          format: date-time
          description: Oldest `failed_at`; omitted when the DLQ is empty.
          example: "2026-09-01T08:12:44Z"
        newest_event:
          type: string
          format: date-time
          description: Newest `failed_at`; omitted when the DLQ is empty.
          example: "2026-09-15T10:30:00Z"
        by_transport:
          type: object
          additionalProperties:
            type: integer
          description: Events per transport.
          example:
            global-webhook: 42
            global-rabbitmq: 15
        by_event_type:
          type: object
          additionalProperties:
            type: integer
          description: Events per event type.
          example:
            Message: 40
            Receipt: 17
    ArchiveEvent:
      type: object
      description: Archived copy of an event whose delivery finished on a transport, delivered (`success`) or failed (`dlq`).
      properties:
        id:
          type: integer
          format: int64
          description: Archive row ID.
          example: 99810
        transport:
          type: string
          description: Transport that handled the event.
          example: global-rabbitmq
        user_id:
          type: string
          description: User ID of the event.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Gateway event type.
          example: Message
        payload:
          type: string
          description: Original event envelope as a JSON string (includes `userToken`).
          example: '{"id":"evt_01J8Z9","eventType":"Message","payload":{"type":"Message"}}'
        status:
          type: string
          description: "`success` for delivered events, `dlq` for failures."
          enum:
            - success
            - dlq
          example: success
        attempts:
          type: integer
          description: Delivery attempts made.
          example: 1
        last_error:
          type: string
          description: Error of the last attempt; omitted when empty.
          example: webhook returned status 503
        failed_at:
          type: string
          format: date-time
          description: Time of the final failure; omitted for delivered events.
          example: "2026-09-15T10:30:00Z"
        created_at:
          type: string
          format: date-time
          description: Time the event was created.
          example: "2026-09-15T12:00:00Z"
        delivered_at:
          type: string
          format: date-time
          description: Time the row was archived (delivery or final failure).
          example: "2026-09-15T12:00:01Z"
        replay_count:
          type: integer
          description: Times the row was replayed.
          example: 0
        last_replay_at:
          type: string
          format: date-time
          description: Time of the last replay; omitted when never replayed.
          example: "2026-09-15T13:00:00Z"
    ArchiveFilter:
      type: object
      description: Filter applied to an archive query, echoed with Go field names. Zero dates mean no date bound.
      properties:
        Status:
          type: string
          description: Status filter; empty for both statuses.
          example: success
        Transport:
          type: string
          description: Transport filter; empty when not set.
          example: global-rabbitmq
        UserID:
          type: string
          description: User ID filter; empty when not set.
          example: bec45bb93cbd24cbec32941ec3c93a12
        EventType:
          type: string
          description: Event type filter; empty when not set.
          example: Message
        FromDate:
          type: string
          format: date-time
          description: Lower `delivered_at` bound; `0001-01-01T00:00:00Z` when not set.
          example: "0001-01-01T00:00:00Z"
        ToDate:
          type: string
          format: date-time
          description: Upper `delivered_at` bound; `0001-01-01T00:00:00Z` when not set.
          example: "0001-01-01T00:00:00Z"
        Limit:
          type: integer
          description: Effective limit.
          example: 50
        Offset:
          type: integer
          description: Effective offset.
          example: 0
    ArchiveStats:
      type: object
      description: Event archive statistics.
      properties:
        total_count:
          type: integer
          format: int64
          description: Archive rows of both statuses.
          example: 99805
        success_count:
          type: integer
          format: int64
          description: Delivered rows.
          example: 99748
        dlq_count:
          type: integer
          format: int64
          description: Failed rows.
          example: 57
        oldest_success:
          type: string
          format: date-time
          description: Oldest delivered row; omitted without delivered rows.
          example: "2026-08-16T00:00:02Z"
        newest_success:
          type: string
          format: date-time
          description: Newest delivered row; omitted without delivered rows.
          example: "2026-09-15T12:00:01Z"
        oldest_dlq:
          type: string
          format: date-time
          description: Oldest failure; omitted without DLQ rows.
          example: "2026-09-01T08:12:44Z"
        newest_dlq:
          type: string
          format: date-time
          description: Newest failure; omitted without DLQ rows.
          example: "2026-09-15T10:30:00Z"
        by_transport:
          type: object
          additionalProperties:
            type: integer
          description: Rows per transport.
          example:
            global-webhook: 50012
            global-rabbitmq: 49793
        by_event_type:
          type: object
          additionalProperties:
            type: integer
          description: Rows per event type.
          example:
            Message: 70100
            ReadReceipt: 29705
        by_status:
          type: object
          additionalProperties:
            type: integer
          description: Rows per status.
          example:
            success: 99748
            dlq: 57
        by_transport_status:
          type: object
          additionalProperties:
            type: object
            additionalProperties:
              type: integer
          description: Rows per transport, then per status.
          example:
            global-webhook:
              success: 49970
              dlq: 42
    MonitoringEvent:
      type: object
      description: Event recorded by the monitoring pipeline for every dispatched gateway event.
      properties:
        id:
          type: integer
          format: int64
          description: Stored event ID (0 when storage is disabled).
          example: 4521
        user_id:
          type: string
          description: User ID the event belongs to.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Gateway event type.
          example: Message
        payload:
          type: object
          additionalProperties: true
          description: Event payload as dispatched to the transports.
          example:
            type: Message
            instanceName: store-support
        status:
          type: string
          description: Processing status.
          enum:
            - pending
            - processing
            - delivered
            - failed
          example: pending
        attempts:
          type: integer
          description: Delivery attempts recorded.
          example: 0
        created_at:
          type: string
          format: date-time
          description: Creation time.
          example: "2026-09-15T12:05:01Z"
        updated_at:
          type: string
          format: date-time
          description: Last update time.
          example: "2026-09-15T12:05:01Z"
    MonitoringEventFilter:
      type: object
      description: Filters for monitoring streams and sessions.
      properties:
        user_id:
          type: string
          description: Only events of this user ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Only events of this event type.
          example: Message
        status:
          type: string
          description: Only events with this processing status.
          enum:
            - pending
            - processing
            - delivered
            - failed
          example: failed
        since_id:
          type: integer
          format: int64
          description: Only events with an ID greater than this value.
          example: 4400
        before_id:
          type: integer
          format: int64
          description: Only events with an ID lower than this value.
          example: 4600
        since_time:
          type: string
          format: date-time
          description: Only events created at or after this time.
          example: "2026-09-15T00:00:00Z"
        limit:
          type: integer
          description: Page size for history queries (0 uses 50, capped at 1000).
          example: 50
        only_failed:
          type: boolean
          description: Only failed events.
          example: true
    MonitoringClientInfo:
      type: object
      description: One connected SSE client.
      properties:
        id:
          type: string
          description: Client connection ID.
          example: client_1757937600123456789_ab12cd34
        session_token:
          type: string
          description: Session token used, or the client ID when connected with the admin header.
          example: session_1757937600123456789_9f8e7d6c5b4a3210
        user_id:
          type: string
          description: User scope; empty for admin clients without a `user_id` filter.
          example: ""
        is_admin:
          type: boolean
          description: The client has admin scope.
          example: true
        connected:
          type: boolean
          description: The client is still connected.
          example: true
        connected_at:
          type: string
          format: date-time
          description: Connection time.
          example: "2026-09-15T12:00:00Z"
        last_event_id:
          type: integer
          format: int64
          description: Last event ID delivered.
          example: 4522
        last_heartbeat:
          type: string
          format: date-time
          description: Last heartbeat time.
          example: "2026-09-15T12:00:00Z"
        connection_time:
          type: number
          description: Seconds since the client connected.
          example: 307.2
        filter:
          $ref: "#/components/schemas/MonitoringEventFilter"
    MonitoringHubStats:
      type: object
      description: SSE hub counters.
      properties:
        connected_clients:
          type: integer
          description: Clients currently connected.
          example: 1
        total_events_broadcast:
          type: integer
          format: int64
          description: Events broadcast since startup.
          example: 1284
        total_events_delivered:
          type: integer
          format: int64
          description: Event deliveries to clients since startup.
          example: 1279
        cache_hits:
          type: integer
          format: int64
          description: Query cache hits.
          example: 542
        cache_misses:
          type: integer
          format: int64
          description: Query cache misses.
          example: 38
        cache_hit_rate:
          type: number
          description: Hit ratio between 0 and 1.
          example: 0.9345
        uptime_seconds:
          type: integer
          format: int64
          description: Seconds since the hub started.
          example: 86400
        start_time:
          type: string
          format: date-time
          description: Hub start time.
          example: "2026-09-14T12:00:00Z"
        last_broadcast_at:
          type: string
          format: date-time
          description: Time of the last broadcast.
          example: "2026-09-15T12:05:07Z"
    MonitoringNotifierStats:
      type: object
      description: Notifier worker queue usage.
      properties:
        enabled:
          type: boolean
          description: The notifier is dispatching events.
          example: true
        workers:
          type: integer
          description: Worker goroutines (`MONITORING_WORKER_COUNT`).
          example: 5
        queue_size:
          type: integer
          description: Events waiting in the queue.
          example: 2
        queue_capacity:
          type: integer
          description: Queue capacity (`MONITORING_EVENT_BUFFER_SIZE`).
          example: 10000
        queue_utilization:
          type: number
          description: Queue usage ratio between 0 and 1.
          example: 0.0002
    MonitoringStatistics:
      type: object
      description: Monitoring subsystem statistics (raw object, no envelope).
      properties:
        hub:
          $ref: "#/components/schemas/MonitoringHubStats"
        notifier:
          $ref: "#/components/schemas/MonitoringNotifierStats"
        events:
          type: object
          description: Stored event counts.
          properties:
            by_status:
              type: object
              additionalProperties:
                type: integer
              description: Stored events per status.
              example:
                delivered: 1200
                failed: 54
            by_type:
              type: object
              additionalProperties:
                type: integer
              description: Stored events for the 10 most frequent event types.
              example:
                Message: 890
                ReadReceipt: 320
        clients:
          type: array
          description: Connected SSE clients.
          items:
            $ref: "#/components/schemas/MonitoringClientInfo"
    MonitoringSessionTokenRequest:
      type: object
      description: Optional filters encoded into the returned stream URL.
      properties:
        filters:
          $ref: "#/components/schemas/MonitoringEventFilter"
    MonitoringSessionToken:
      type: object
      description: Monitoring session token (raw object, no envelope).
      properties:
        session_token:
          type: string
          description: Token for the `session` query parameter of the monitoring routes.
          example: session_1757937600123456789_9f8e7d6c5b4a3210
        expires_at:
          type: string
          format: date-time
          description: Expiration time of the token.
          example: "2026-09-15T12:05:00.123456789Z"
        stream_url:
          type: string
          description: Absolute stream URL with the session token and filters.
          example: https://api.example.com/admin/events/stream?session=session_1757937600123456789_9f8e7d6c5b4a3210
        ttl_seconds:
          type: integer
          description: Token lifetime in seconds.
          example: 300
    ReplayRequest:
      type: object
      description: Single archive replay (`event_id`) or bulk replay (filters). All fields are optional.
      properties:
        event_id:
          type: integer
          format: int64
          description: Archive row ID to replay. When present, the filters and `limit` are ignored.
          example: 99810
        dry_run:
          type: boolean
          default: false
          description: Preview only; nothing is re-enqueued.
          example: false
        status:
          type: string
          enum:
            - dlq
            - success
          description: Bulk filter by status; omit for both.
          example: success
        transport:
          type: string
          description: Bulk filter by transport.
          example: global-rabbitmq
        user_id:
          type: string
          description: Bulk filter by user ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Bulk filter by event type.
          example: Message
        limit:
          type: integer
          default: 50
          maximum: 1000
          description: Maximum rows replayed in bulk mode (0 uses 50, values above 1000 are capped).
          example: 1000
    DeleteBulkRequest:
      type: object
      description: Filters for bulk archive deletion. At least one of `status`, `transport`, `user_id` or `event_type` is required.
      properties:
        status:
          type: string
          enum:
            - dlq
            - success
          description: Delete rows with this status.
          example: success
        transport:
          type: string
          description: Delete rows of this transport.
          example: global-rabbitmq
        user_id:
          type: string
          description: Delete rows of this user ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Delete rows of this event type.
          example: Message
        limit:
          type: integer
          default: 50
          maximum: 1000
          description: Maximum rows deleted in this call (0 uses 50, values above 1000 are capped).
          example: 1000
    User:
      type: object
      description: Stored user (instance) with its full configuration, as returned by the admin list routes.
      properties:
        id:
          type: string
          description: Stored user ID (32 hexadecimal characters).
          example: bec45bb93cbd24cbec32941ec3c93a12
        name:
          type: string
          description: Display name of the instance.
          example: store-support
        avatar_url:
          type: string
          description: WhatsApp profile picture URL (cached). Empty when unknown.
          example: https://pps.whatsapp.net/v/t61.24694-24/example.jpg
        token:
          type: string
          description: API token of the instance, returned unmasked.
          example: a1b2c3d4e5f6a7b8c9d0
        webhook:
          type: string
          description: Per-user webhook URL. Empty when not configured.
          example: https://hooks.example.com/zuckzapgo
        jid:
          type: string
          description: JID of the paired WhatsApp device, including the device suffix. Empty before pairing.
          example: "5511999999999:12@s.whatsapp.net"
        qrcode:
          type: string
          description: Last stored pairing QR code (base64 PNG data URL). Empty once paired.
          example: ""
        connected:
          type: boolean
          description: Live flag from the in-memory client; `false` when this process has no client for the user.
          example: true
        loggedIn:
          type: boolean
          description: Live flag from the in-memory client; `false` when this process has no client for the user.
          example: true
        expiration:
          type: integer
          format: int64
          description: Stored expiration value (0 when not set).
          example: 0
        proxy_url:
          type: string
          description: Stored legacy proxy URL. Empty when not configured.
          example: ""
        events:
          type: string
          description: Comma-separated subscribed event types, or `All`.
          example: Message,ReadReceipt,Connected
        skip_media_download:
          type: boolean
          description: Media is not downloaded or inlined in events.
          example: false
        skip_groups:
          type: boolean
          description: Events from group chats are skipped.
          example: true
        skip_newsletters:
          type: boolean
          description: Events from newsletters (channels) are skipped.
          example: true
        skip_broadcasts:
          type: boolean
          description: Broadcast and status events are skipped.
          example: true
        skip_own_messages:
          type: boolean
          description: Messages sent by the instance itself are skipped.
          example: false
        echo_api_messages:
          type: boolean
          description: Messages sent through the API emit synthetic Message events (also requires `ECHO_API_MESSAGES_ENABLED=true`).
          example: false
        skip_calls:
          type: boolean
          description: Call events are skipped and incoming calls are rejected.
          example: false
        call_reject_message:
          type: string
          description: Message sent when an incoming call is rejected.
          example: Sorry, I cannot take calls at the moment.
        call_reject_type:
          type: string
          description: Call rejection type.
          example: busy
        globalTransportSkips:
          $ref: "#/components/schemas/GlobalTransportSkipFlags"
        isFromAPI:
          type: boolean
          description: Mirrors `echo_api_messages`.
          example: false
        auto_presence_unavailable:
          type: boolean
          description: The instance marks itself unavailable automatically after connecting.
          example: false
        proxy_config:
          $ref: "#/components/schemas/AdminUserProxyConfig"
        s3_config:
          $ref: "#/components/schemas/AdminUserS3Config"
        rabbitmq_config:
          $ref: "#/components/schemas/AdminUserRabbitMQConfig"
        whatsapp_config:
          $ref: "#/components/schemas/AdminUserWhatsAppConfig"
    NetworkRouteRequest:
      type: object
      additionalProperties: false
      description: |
        Desired native per-session mobile egress, prepared before the new instance may connect. `authKey` is a write-only, one-use Tailscale enrollment key that is never stored, returned or logged.
      required:
        - mode
        - authKey
        - exitNodeId
        - routeVersion
        - idempotencyKey
        - tcpOnly
      properties:
        mode:
          type: string
          enum:
            - mobile
          description: Only `mobile` is accepted (400 `network_route_mode_invalid` otherwise). Case and surrounding spaces are normalized.
          example: mobile
        authKey:
          type: string
          format: password
          writeOnly: true
          minLength: 12
          maxLength: 512
          pattern: ^tskey-auth-\S+$
          description: One-use Tailscale auth key starting with `tskey-auth-`, without spaces or JSON escapes (400 `network_route_auth_key_invalid` otherwise).
          example: tskey-auth-REDACTED
        exitNodeId:
          type: string
          minLength: 3
          maxLength: 128
          pattern: ^[A-Za-z0-9_-]+$
          description: Stable Tailscale node ID of the exit node, never a hostname or IP (400 `network_route_exit_node_invalid` otherwise).
          example: nStableMobileExit01
        routeVersion:
          type: integer
          format: int64
          minimum: 1
          description: Positive, monotonically increasing fencing token for this session (400 `network_route_version_invalid` otherwise).
          example: 1
        idempotencyKey:
          type: string
          minLength: 8
          maxLength: 160
          pattern: ^[A-Za-z0-9._:-]+$
          description: Identifies exactly one desired version; reuse it only for an identical retry (400 `network_route_idempotency_key_invalid` otherwise).
          example: provision-pilot-0001
        failClosed:
          type: boolean
          description: "`true` holds WhatsApp traffic while the route is not ready. `false` lets the session use the default egress meanwhile, so WhatsApp sees another IP; it is never inferred."
          example: true
        tcpOnly:
          type: boolean
          enum:
            - true
          description: Must be `true` (400 `network_route_mobile_requires_tcp_only` otherwise). Native calls over UDP are unavailable while the route is desired.
          example: true
    NetworkRouteView:
      type: object
      additionalProperties: false
      description: Non-secret route state plus in-process readiness. Enrollment keys and SOCKS credentials are never included.
      required:
        - mode
        - exitNodeId
        - desiredVersion
        - appliedVersion
        - failClosed
        - tcpOnly
        - status
        - ready
      properties:
        mode:
          type: string
          description: Route mode; `mobile` for stored routes, empty in the zero state of validation errors.
          example: mobile
        exitNodeId:
          type: string
          description: Stable Tailscale exit-node ID.
          example: nStableMobileExit01
        desiredVersion:
          type: integer
          format: int64
          description: Last accepted `routeVersion`.
          example: 3
        appliedVersion:
          type: integer
          format: int64
          description: Last version whose node start, exit selection, loopback SOCKS and TLS preflight completed.
          example: 3
        failClosed:
          type: boolean
          description: "`true` holds traffic while the route is not ready; `false` lets the session use the default egress."
          example: true
        tcpOnly:
          type: boolean
          description: Always `true` for stored mobile routes (native Calls/UDP disabled).
          example: true
        status:
          type: string
          description: Stored lifecycle status; empty in the zero state of validation errors.
          enum:
            - preparing
            - ready
            - blocked
            - ""
          example: ready
        ready:
          type: boolean
          description: "`true` only when versions match, `status` is `ready` and the node for that version is live in this process."
          example: true
        errorCode:
          type: string
          description: Safe error code of the last failure; omitted when empty.
          example: network_route_prepare_failed
    NetworkRouteApplyResponse:
      type: object
      description: Success envelope of `POST` and `DELETE /session/network-route`, with `details` and `reconnectScheduled` at the top level.
      required:
        - code
        - data
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 200
        data:
          $ref: "#/components/schemas/NetworkRouteView"
        details:
          type: string
          description: "`network route ready` after an apply, or the fallback explanation after a removal."
          example: network route ready
        reconnectScheduled:
          type: boolean
          description: "`true` when the gateway queued this session to reconnect: after an apply when a previously connected session resumes on the ready route; after a removal when the session was connected or a fail-closed route that never became ready was holding it offline."
          example: true
        success:
          type: boolean
          description: Always `true`.
          example: true
    NetworkRouteGetResponse:
      type: object
      description: Success envelope of `GET /session/network-route`. `egressIP` or `egressProbeError` appear at the top level only with `probe=egress`.
      required:
        - code
        - data
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 200
        data:
          $ref: "#/components/schemas/NetworkRouteView"
        egressIP:
          type: string
          description: Public IP observed through the route; only when the egress probe succeeded.
          example: 203.0.113.45
        egressProbeError:
          type: string
          description: Set instead of `egressIP` when the egress probe failed.
          enum:
            - network_route_egress_probe_failed
          example: network_route_egress_probe_failed
        success:
          type: boolean
          description: Always `true`.
          example: true
    NetworkRouteErrorResponse:
      type: object
      description: Error envelope of the network-route endpoints. Errors raised by the route manager also include `data` (the stored route, or an empty state for validation errors) and, when a cause exists, a redacted `details`.
      required:
        - code
        - error
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 503
        data:
          $ref: "#/components/schemas/NetworkRouteView"
        error:
          type: string
          description: Stable machine-readable error code.
          enum:
            - network_route_payload_invalid
            - network_route_mode_invalid
            - network_route_mobile_requires_tcp_only
            - network_route_version_invalid
            - network_route_exit_node_invalid
            - network_route_idempotency_key_invalid
            - network_route_auth_key_invalid
            - network_route_user_invalid
            - network_route_invalid
            - network_route_conflicts_with_proxy
            - network_route_stale_version
            - network_route_version_conflict
            - network_route_block_failed
            - network_route_not_ready
            - network_route_not_configured
            - network_route_manager_unavailable
            - network_route_store_unavailable
            - network_route_prepare_failed
            - network_route_restore_failed
            - network_route_revoke_failed
            - network_route_fence_lost
            - network_route_unavailable
            - network_route_ready_invariant_failed
          example: network_route_prepare_failed
        details:
          type: string
          description: Redacted cause (auth keys and URL credentials removed, at most 300 characters); omitted when there is no cause.
          example: exit node nStableMobileExit01 is offline
        success:
          type: boolean
          description: Always `false`.
          example: false
    CreateUserResponse:
      description: Success envelope of `POST /admin/users` (HTTP 201).
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/AdminCreatedUser"
    CreateUserNetworkRouteFailureResponse:
      type: object
      description: 503 body of `POST /admin/users`. With `data`, the user row exists but is blocked because the native route was not prepared; without `data`, nothing was stored.
      required:
        - code
        - error
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 503
        data:
          type: object
          description: Present only when the user was stored in blocked state.
          properties:
            userId:
              type: string
              description: ID of the stored, blocked user. Use it to retry the route or `DELETE /admin/users/{id}`. The token is deliberately absent.
              example: bec45bb93cbd24cbec32941ec3c93a12
            network_route:
              $ref: "#/components/schemas/NetworkRouteView"
        error:
          type: string
          description: Safe error code.
          example: network_route_prepare_failed
        details:
          type: string
          description: Remediation hint, present with `data`.
          example: user persisted in blocked state; submit a higher routeVersion with a new one-use auth key or delete this user
        success:
          type: boolean
          description: Always false for errors.
          example: false
    CreateUser:
      type: object
      description: New user (instance) settings. Only `name` and `token` are expected; everything else is optional.
      required:
        - name
        - token
      properties:
        name:
          type: string
          description: Display name of the instance. Not validated for uniqueness or emptiness.
          example: store-support
        token:
          type: string
          description: API token the instance authenticates with (`token` header). Must be unique (409 otherwise).
          example: a1b2c3d4e5f6a7b8c9d0
        webhook:
          type: string
          description: Per-user webhook URL that receives events.
          example: https://hooks.example.com/zuckzapgo
        expiration:
          type: integer
          description: Expiration value stored as sent (0 when omitted).
          example: 0
        events:
          type: string
          description: Comma-separated event types to subscribe to, or `All`. Names are case-sensitive and must be supported event types; spaces around names are ignored during validation.
          example: Message,ReadReceipt,Connected
        echoApiMessages:
          type: boolean
          description: Messages sent through the API emit synthetic Message events (also requires `ECHO_API_MESSAGES_ENABLED=true`).
          example: false
        skipMedia:
          type: boolean
          description: Do not download or inline media in events.
          example: false
        skipGroups:
          type: boolean
          description: Skip events from group chats.
          example: false
        skipNewsletters:
          type: boolean
          description: Skip events from newsletters (channels).
          example: false
        skipBroadcasts:
          type: boolean
          description: Skip broadcast and status events.
          example: false
        skipOwnMessages:
          type: boolean
          description: Skip messages sent by the instance itself.
          example: false
        skipCalls:
          type: boolean
          description: Skip call events and reject incoming calls.
          example: false
        callRejectMessage:
          type: string
          description: Message sent when rejecting a call. Defaults to `Sorry, I cannot take calls at the moment.`
          example: Sorry, I cannot take calls at the moment.
        callRejectType:
          type: string
          description: Call rejection type. Defaults to `busy`. Stored as sent.
          enum:
            - busy
            - decline
            - unavailable
          example: busy
        autoPresenceUnavailable:
          type: boolean
          description: Mark the instance unavailable automatically after connecting.
          example: false
        globalTransportSkips:
          $ref: "#/components/schemas/GlobalTransportSkipsRequest"
        networkRoute:
          $ref: "#/components/schemas/NetworkRouteRequest"
        proxyConfig:
          $ref: "#/components/schemas/CreateUserProxyConfig"
        s3Config:
          $ref: "#/components/schemas/CreateUserS3Config"
        rabbitmqConfig:
          $ref: "#/components/schemas/CreateUserRabbitMQConfig"
        whatsappConfig:
          $ref: "#/components/schemas/CreateUserWhatsAppConfig"
    GlobalTransportSkipsRequest:
      type: object
      description: Partial update of the per-instance global transport opt-out flags. Omitted flags keep their current value; unknown fields are ignored.
      properties:
        skipGlobalWebhook:
          type: boolean
          example: false
          description: "`true` stops the global webhook from receiving this instance's events."
        skipGlobalRabbitMQ:
          type: boolean
          example: true
          description: "`true` stops the global RabbitMQ publisher from receiving this instance's events."
        skipGlobalSQS:
          type: boolean
          example: false
          description: "`true` stops the global SQS publisher from receiving this instance's events."
        skipGlobalRedis:
          type: boolean
          example: false
          description: "`true` stops the global Redis publisher from receiving this instance's events."
        skipGlobalWebSocket:
          type: boolean
          example: false
          description: "`true` stops the global WebSocket broadcaster from receiving this instance's events."
        skipGlobalS3:
          type: boolean
          example: true
          description: "`true` skips the global S3 media upload/enrichment for this instance's events."
    GlobalTransportSkipFlags:
      type: object
      description: Per-instance global transport opt-out flags. `true` means the global dispatcher skips this instance for that transport.
      properties:
        skipGlobalWebhook:
          type: boolean
          description: Global webhook skipped for this instance.
          example: false
        skipGlobalRabbitMQ:
          type: boolean
          description: Global RabbitMQ skipped for this instance.
          example: true
        skipGlobalSQS:
          type: boolean
          description: Global SQS skipped for this instance.
          example: false
        skipGlobalRedis:
          type: boolean
          description: Global Redis skipped for this instance.
          example: false
        skipGlobalWebSocket:
          type: boolean
          description: Global WebSocket skipped for this instance.
          example: false
        skipGlobalS3:
          type: boolean
          description: Global S3 media enrichment skipped for this instance.
          example: true
    GlobalTransportSkipsState:
      description: All six opt-out flags plus a confirmation message.
      allOf:
        - $ref: "#/components/schemas/GlobalTransportSkipFlags"
        - type: object
          properties:
            details:
              type: string
              description: Human-readable confirmation.
              example: Global transport skip configuration retrieved successfully
    GlobalTransportSkipsResponse:
      description: Success envelope of `GET` and `POST /session/globaltransports/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GlobalTransportSkipsState"
    EchoAPISettingsRequest:
      type: object
      description: Per-instance API message echo flag.
      properties:
        enabled:
          type: boolean
          example: true
          description: "`true` emits a `Message` event (with `Info.IsFromAPI: true`) for each message sent through the API, when `ECHO_API_MESSAGES_ENABLED` is also true. Omitted is treated as `false`."
    EchoAPISettingsResponse:
      description: Success envelope of `GET /session/echo/api`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored echo flag.
              properties:
                EchoAPIMessages:
                  type: boolean
                  description: Whether API sends emit `Message` events for this instance.
                  example: true
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Echo API messages configuration retrieved successfully
    DeleteUser:
      type: object
      properties:
        id:
          type: string
          example: 4e4942c7dee1deef99ab8fd9f7350de5
    GroupPhoto:
      type: object
      description: Group and the new JPEG photo. JSON keys are matched case-insensitively.
      required:
        - GroupJID
        - Image
      properties:
        GroupJID:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        Image:
          type: string
          description: "JPEG image as a data URL (`data:image/jpeg;base64,...`). The decoded bytes must be a JPEG file."
          example: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AKp//2Q=="
    GroupInfo:
      type: object
      properties:
        GroupJID:
          type: string
          example: 120363000000000016@g.us
    GroupInviteLink:
      type: object
      properties:
        GroupJID:
          type: string
          example: 120363000000000016@g.us
        Reset:
          type: boolean
          example: false
    Connect:
      type: object
      description: Event subscription and wait mode for `POST /session/connect` and `POST /connector/connect`. Field names are matched case-insensitively.
      properties:
        Subscribe:
          type: array
          description: Event types for the individual webhook. Replaces the stored subscription on every call; unknown names are discarded; omitted or empty stores an empty subscription. Use `["All"]` for every event.
          items:
            type: string
            description: Event type name from `supportedEventTypes`.
            example: Message
          example:
            - Message
            - ReadReceipt
            - Connected
        Immediate:
          type: boolean
          default: false
          description: "`true` returns right after starting the client. `false` waits about 10 seconds and returns 500 `failed to connect` when the socket is not connected by then."
          example: true
    DownloadImage:
      type: object
      required:
        - DirectPath
        - MediaKey
        - FileEncSHA256
        - FileSHA256
      properties:
        Url:
          type: string
          description: "`URL` of the media message in the webhook payload. Accepted for compatibility but not used: the file is fetched through `DirectPath`."
          example: https://mmg.whatsapp.net/o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c
        DirectPath:
          type: string
          description: "`directPath` from the webhook media message (must start with `/`). Required in practice: without it the download fails with `no url present`."
          example: /o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c
        MediaKey:
          type: string
          description: "`mediaKey` from the webhook media message as a standard base64 string (not hex). Used to decrypt the file."
          example: aQGcesqhANsFce2gBbNeXnE4LU9wA6LM5lA1ztbBduM=
        Mimetype:
          type: string
          description: "`mimetype` from the webhook media message. Not sent to WhatsApp; echoed in the response and used as the data URL media type."
          example: image/jpeg
        FileEncSHA256:
          type: string
          description: "`fileEncSHA256` from the webhook media message as standard base64: SHA-256 of the encrypted file."
          example: 8wdTCF7kBugCQhneOg2YLsDye4Eqwu7N8kzyXKJXoIU=
        FileSHA256:
          type: string
          description: "`fileSHA256` from the webhook media message as standard base64: SHA-256 of the decrypted file. A mismatch fails with `hash of media plaintext doesn't match`."
          example: Gc6NIzcYSI596Ke0Y0sO1VstRgq8FpAUzOglP06aYyE=
        FileLength:
          type: integer
          description: "`fileLength` (bytes) from the webhook media message. Accepted but not used for the download."
          example: 48213
      description: Media reference copied from the `Message` webhook event (e.g. `event.Message.imageMessage`). Keys are matched case-insensitively, so the webhook object (`URL`, `directPath`, `mediaKey`, ...) can be posted unchanged. In practice `DirectPath`, `MediaKey`, `FileEncSHA256` and `FileSHA256` are what the download needs; `Url` and `FileLength` are not used.
    ChatPresence:
      type: object
      required:
        - Phone
        - State
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: "Chat to show the indicator in: phone number or JID (`@s.whatsapp.net`, `@lid`, `@g.us`)."
        State:
          type: string
          example: composing
          description: "`composing` (typing, or recording with `Media: audio`) or `paused` (clears the indicator). Sent as-is, not validated."
        Media:
          type: string
          example: audio
          description: Optional. `audio` shows "recording audio" instead of typing; omit or leave empty for text.
    UserPresence:
      type: object
      description: Global presence to send.
      required:
        - type
      properties:
        type:
          type: string
          description: "`available` (online) or `unavailable` (offline)."
          enum:
            - available
            - unavailable
          example: available
    MessageContact:
      type: object
      required:
        - Phone
        - Name
        - Vcard
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code
        Name:
          type: string
          example: John Doe
          description: Contact display name
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID
        Vcard:
          type: string
          example: |-
            BEGIN:VCARD
            VERSION:3.0
            N:Doe;John;;;
            FN:John Doe
            ORG:Example.com Inc.;
            TITLE:Imaginary test person
            EMAIL;type=INTERNET;type=WORK;type=pref:johnDoe@example.org
            TEL;type=WORK;type=pref:+1 617 555 1212
            TEL;type=WORK:+1 (617) 555-1234
            TEL;type=CELL:+1 781 555 1212
            TEL;type=HOME:+1 202 555 1212
            item1.ADR;type=WORK:;;2 Enterprise Avenue;Worktown;NY;01111;USA
            item1.X-ABADR:us
            item2.ADR;type=HOME;type=pref:;;3 Acacia Avenue;Hoitem2.X-ABADR:us
            END:VCARD
          description: Contact information in vCard format
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator
        Duration:
          type: integer
          example: 86400
          description: Accepted but not applied by /chat/send/contact.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageLocation:
      type: object
      required:
        - Phone
        - Latitude
        - Longitude
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code
        Name:
          type: string
          example: Eiffel Tower
          description: Optional location name or description
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID
        Latitude:
          type: number
          format: float
          example: 48.85837
          description: Latitude in decimal degrees. Exactly 0 is treated as missing (400).
        Longitude:
          type: number
          format: float
          example: 2.294481
          description: Longitude in decimal degrees. Exactly 0 is treated as missing (400).
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator
        Duration:
          type: integer
          example: 86400
          description: Accepted but not applied by /chat/send/location.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    ReactionText:
      type: object
      required:
        - Phone
        - Body
        - Id
      properties:
        Phone:
          type: string
          description: |
            Destination chat. Private: number in international format without '+' (e.g. 5511999999999).
            Group: the group JID (e.g. 120363000000000013@g.us).
          example: "5511999999999"
        Body:
          type: string
          description: Reaction emoji. Use an empty string ("") or "remove" to REMOVE the reaction.
          example: ❤️
        Id:
          type: string
          description: |
            Target message ID. Prefix with 'me:' when the message was sent by YOUR instance
            (fromMe). For messages received from others, use the raw ID (no prefix).
          example: me:3EB06F9067F80BAB89FF
        Sender:
          type: string
          description: |
            (GROUP only, when reacting to SOMEONE ELSE's message) JID of the author of the message
            being reacted to. Use the Info.Sender value delivered by the webhook — in LID groups it
            comes as an '@lid' JID (e.g. 123456789012345@lid). Helps iOS correlate the reaction to
            its target message inside the group.
          example: 123456789012345@lid
        Chat:
          type: string
          description: |
            (Optional) Exact chat JID as the recipient's device stores it — typically the Info.Chat
            value from the webhook, which for chats migrated to LID comes as an '@lid' JID
            (e.g. 123456789012345@lid). When provided, it is used as BOTH the send destination and
            the reaction key's RemoteJID, keeping the envelope and the key in the same namespace
            (PN/LID) — this guarantees the reaction renders on iOS without relying on the automatic
            resolution. When omitted, Phone (PN) is used and PN→LID resolution is applied
            automatically for 1:1 chats.
          example: 123456789012345@lid
    PollVote:
      type: object
      required:
        - phone
        - poll_message_id
        - options
      properties:
        phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code, or a group JID (120363...@g.us). The vote is delivered to the chat the poll was created in.
        poll_message_id:
          type: string
          example: 90B2F8B13FAC8A9CF6B06E99C7834DC5
          description: Message ID of the poll's CREATION message (not of a vote).
        options:
          type: array
          description: Option names to vote for. Must match the poll's own options exactly. More than one is only valid on a multi-select poll. An EMPTY array retracts the vote.
          items:
            type: string
          example:
            - "09:00"
    EventResponse:
      type: object
      required:
        - phone
        - event_message_id
        - response
      properties:
        phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code, or a group JID. The RSVP is delivered to the chat the event was created in.
        event_message_id:
          type: string
          example: 90B2F8B13FAC8A9CF6B06E99C7834DC5
          description: Message ID of the event's CREATION message.
        response:
          type: string
          enum:
            - GOING
            - NOT_GOING
            - MAYBE
            - UNKNOWN
          example: GOING
          description: The RSVP. UNKNOWN removes a previous response. Anything outside this set is rejected with 400.
        extra_guest_count:
          type: integer
          format: int32
          default: 0
          example: 1
          description: "Deprecated: use with_guest. Still accepted for compatibility: 0 = no guest, >= 1 is sent as one guest (1). Only applies to GOING. Cannot be negative (400)."
          deprecated: true
        with_guest:
          type: boolean
          example: true
          description: 'Recommended. true = "Going with guest" (the official app sends a single guest, extraGuestCount=1). Only applies to GOING; ignored otherwise. Takes precedence over extra_guest_count. Alias: WithGuest.'
    MessagePoll:
      type: object
      required:
        - Group
        - Header
        - Options
      properties:
        Group:
          type: string
          example: 120363000000000012@g.us
          description: Group JID where to send the poll
        Header:
          type: string
          example: What's your favorite color?
          description: Poll question/header text
        Options:
          type: array
          description: Array of poll options (minimum 2, maximum 12)
          items:
            type: string
            example: Red
          minItems: 2
          maxItems: 12
        MaxAnswer:
          type: integer
          example: 1
          description: Maximum number of answers per user. If not set to 1, all options will be available for selection (accepts both 'max_answer' and 'MaxAnswer' for compatibility)
        Id:
          type: string
          example: 3EB06F9067F80BAB89FF
          description: Optional custom message ID
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator (accepts both 'presence' and 'Presence' for compatibility)
        Duration:
          type: integer
          example: 86400
          description: Message expiration time in seconds (accepts both 'duration' and 'Duration' for compatibility)
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageList:
      type: object
      required:
        - Phone
        - Text
        - ButtonText
        - Sections
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code. Must include country code without + symbol (e.g., 5511999999999).
        Id:
          type: string
          example: LIST_001
          description: Optional custom message ID. If not provided, a random one will be generated.
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator (accepts both 'presence' and 'Presence' for compatibility)
        Text:
          type: string
          example: Choose one of the options below
          description: Main list description text. Maximum 1024 characters. This explains what the list is about.
        Title:
          type: string
          example: Product Catalog
          description: Optional list title/header. Maximum 60 characters. Displayed at the top of the list.
        Footer:
          type: string
          example: Powered by ZuckZapGo API
          description: Optional footer text. Maximum 60 characters. Displayed at the bottom of the list.
        ButtonText:
          type: string
          example: View Options
          description: Text for the main action button that opens the list. Maximum 20 characters.
        Sections:
          type: array
          description: Array of list sections. Maximum 10 sections allowed.
          maxItems: 10
          items:
            type: object
            required:
              - Title
              - Rows
            properties:
              Title:
                type: string
                example: Main Products
                description: Section title. Maximum 24 characters.
              Rows:
                type: array
                description: Array of rows in this section. Maximum 10 rows per section.
                maxItems: 10
                items:
                  type: object
                  required:
                    - RowId
                    - Title
                    - Description
                  properties:
                    RowId:
                      type: string
                      example: product_1
                      description: Unique identifier for this row. Used in response callbacks.
                    Title:
                      type: string
                      example: Premium Package
                      description: Row title/name. Maximum 24 characters.
                    Description:
                      type: string
                      example: Complete package with all features included
                      description: Row description. Maximum 72 characters.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageText:
      type: object
      required:
        - Phone
        - Body
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code. Must include country code without + symbol (e.g., 5511999999999).
        Body:
          type: string
          example: How you doin
          description: Text message content. Supports Unicode, emojis, and formatting. Maximum 4096 characters.
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID. If not provided, a random one will be generated.
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator before sending message. Useful for natural conversation flow.
        Duration:
          type: integer
          example: 86400
          description: "Message expiration time in seconds. Valid values: 86400 (24h), 604800 (7 days), 7776000 (90 days)."
        LinkPreview:
          type: boolean
          example: true
          description: Set to true to enable automatic link preview for URLs in the message text.
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
      example:
        Phone: "5511999999999"
        Body: This is my reply to your message
        Id: ABCDABCD1234
        Presence: 3000
        Duration: 86400
        LinkPreview: true
        NumberCheck: true
        EchoApi: false
        ContextInfo:
          stanzaID: 3EB06F9067F80BAB89FF
          participant: 5511888888888@s.whatsapp.net
          quotedMessage:
            conversation: Original message text being replied to
          isForwarded: false
          mentionedJID:
            - 5511999999999@s.whatsapp.net
          mentionAll: false
    MessageLink:
      type: object
      required:
        - Phone
        - Link
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code. Must include country code without + symbol (e.g., 5511999999999).
        Link:
          type: string
          example: https://example.com
          description: Link to send. Must be a valid URL.
        Caption:
          type: string
          example: Link Description
          description: Optional caption for the link. Maximum 1024 characters. Supports Unicode and emojis.
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID. If not provided, a random one will be generated.
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        DisablePreview:
          type: boolean
          example: false
          description: Optional. Set to true to send the link as plain text without any preview card. When enabled, no auto-scrape happens and any `Preview` object is ignored. Useful when the URL is sensitive (tracker links) or when the auto-scraped preview is undesired.
        Preview:
          $ref: "#/components/schemas/LinkPreviewOverride"
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
      example:
        Phone: "5511999999999"
        Link: https://example.com/promo
        Caption: Check out this deal
        Id: ABCDABCD1234
        NumberCheck: false
        EchoApi: false
        DisablePreview: false
        Preview:
          title: "Coupon: 50% off everything"
          description: Use code SAVE50 at checkout
          image: data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
          matched_text: https://example.com/promo
          type: image
          fetch_missing: false
    LinkPreviewOverride:
      type: object
      description: |
        Optional custom link preview to override the default URL auto-scrape.
        When omitted, the link is auto-scraped for OpenGraph metadata (legacy behavior).

        Modes (controlled by `fetch_missing`):
        - `fetch_missing` omitted or false → REPLACE mode: only the fields you set
          are sent; no scraping is performed.
        - `fetch_missing: true` → MERGE mode: the URL is scraped first, then any
          non-empty field you provide overrides the scraped value.

        Tip: use REPLACE for coupon/promo links where you want full control of
        the card; use MERGE when you only want to fix one field (e.g., image).
      properties:
        title:
          type: string
          example: "Coupon: 50% off everything"
          description: Title shown on the preview card.
        description:
          type: string
          example: Use code SAVE50 at checkout.
          description: Short description shown below the title.
        image:
          type: string
          example: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAUDBA... (1200x630 JPEG)
          description: |
            Custom thumbnail that REPLACES the image WhatsApp would auto-generate
            from the link. Accepts a base64 data URL (≤ 5 MB) OR an http(s) URL
            (≤ 8 MB). PNG / WebP / GIF are accepted and re-encoded to JPEG
            server-side (transparency flattened on white).

            To render identically on WhatsApp **mobile AND Web/Desktop**, send the
            format WhatsApp expects:
            • Format: JPEG (always re-encoded to JPEG; the wire field is `jpegThumbnail`).
            • Aspect ratio: 1.91:1 landscape (OpenGraph `og:image` standard).
            • Recommended size: 1200 × 630 px (min ~400 px on the long edge).
            The server uploads a high-res JPEG (downscaled to max 800 px) AND embeds
            a small inline JPEG (~200 px); Web paints the inline one first, so both
            are produced automatically — you only need to supply one good landscape
            image. Square images ≤ 200 px are treated as favicons (smaller card).
        matched_text:
          type: string
          example: https://example.com/promo
          description: |
            The substring of the message body that WhatsApp highlights as the
            clickable link. Defaults to `Link` if omitted. IMPORTANT: it MUST
            appear verbatim inside the message text (caption + link) — WhatsApp
            Web only attaches the card when `matched_text` is found literally in
            the body. If the value is not a substring of the text, the server
            falls back to `Link` so the card keeps rendering on Web.
        type:
          type: string
          enum:
            - image
            - video
            - none
            - placeholder
          default: image
          example: image
          description: |
            Preview card style:
            • `image` (default) — standard OpenGraph-style card with thumbnail.
              Renders on BOTH mobile and WhatsApp Web/Desktop. (Emitted on the wire
              as PreviewType `NONE`, which is what the official WhatsApp clients use
              for image cards; the older `IMAGE` type did not render on Web.)
            • `video` — card with a play affordance for video links (e.g. YouTube);
              `do_not_play_inline` controls inline playback.
            • `none` — no special preview styling (to send the link as plain text
              with no card at all, use `DisablePreview: true`).
            • `placeholder` — neutral card without a thumbnail (opt-in; for
              guaranteed web+mobile parity prefer `image`/`video`).
        fetch_missing:
          type: boolean
          example: false
          description: REPLACE vs MERGE switch. false/omitted = REPLACE (no scrape). true = MERGE (scrape, then override wins on non-empty fields).
        thumbnail_width:
          type: integer
          example: 1200
          description: "Optional explicit thumbnail width in pixels. When omitted, derived from the decoded image. Recommended pairing: 1200 × 630 (1.91:1)."
        thumbnail_height:
          type: integer
          example: 630
          description: "Optional explicit thumbnail height in pixels. When omitted, derived from the decoded image. Recommended pairing: 1200 × 630 (1.91:1)."
        do_not_play_inline:
          type: boolean
          example: false
          description: When `type=video`, set to true to force the WhatsApp client to open the link in the browser instead of trying to play inline.
    MessageImage:
      type: object
      required:
        - Phone
        - Image
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code. Must include country code without + symbol (e.g., 5511999999999).
        Image:
          type: string
          example: data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==
          description: "Image data as base64 data URL (data:image/jpeg;base64,xxx) or HTTP(S) URL (https://example.com/image.jpg). Supported formats: JPEG, PNG, GIF, WebP. Maximum size: 5MB."
        Caption:
          type: string
          example: Image Description
          description: Optional caption for the image. Maximum 1024 characters. Supports Unicode and emojis.
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID. If not provided, a random one will be generated.
        MimeType:
          type: string
          example: image/jpeg
          description: "Optional MIME type override. Supported: image/jpeg, image/png, image/gif, image/webp. Auto-detected if not provided."
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator before sending message.
        ViewOnce:
          type: boolean
          example: true
          description: Set to true for disappearing message after viewing (view once). Cannot be combined with Duration.
        Duration:
          type: integer
          example: 86400
          description: "Message expiration time in seconds. Valid values: 86400 (24h), 604800 (7 days), 7776000 (90 days)."
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageAudio:
      type: object
      required:
        - Phone
        - Audio
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code. Must include country code without + symbol (e.g., 5511999999999).
        Audio:
          type: string
          example: data:audio/ogg;base64,T2dnUwACAAAAAAAAAABK
          description: "Audio data as base64 data URL (data:audio/ogg;base64,xxx) or HTTP(S) URL (https://example.com/audio.ogg). Supported formats: OGG, MP3, AAC, M4A, WAV. Maximum size: 16MB."
        Caption:
          type: string
          example: Voice message caption
          description: "Accepted for compatibility but not delivered: WhatsApp audio messages have no caption."
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID. If not provided, a random one will be generated.
        PTT:
          type: boolean
          example: true
          description: "Set to true for voice note (push-to-talk), false for audio file. Voice notes show with different UI and waveform. Default: true."
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate recording indicator before sending message. Useful for natural conversation flow.
        ViewOnce:
          type: boolean
          example: true
          description: Set to true for disappearing message after viewing (view once). Cannot be combined with Duration.
        Duration:
          type: integer
          example: 86400
          description: "Message expiration time in seconds. Valid values: 86400 (24h), 604800 (7 days), 7776000 (90 days)."
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageVideo:
      type: object
      required:
        - Phone
        - Video
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code (accepts both 'phone' and 'Phone' for compatibility)
        Video:
          type: string
          example: data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDE=
          description: Video data as base64 data URL (data:video/mp4;base64,xxx) or HTTP(S) URL (https://example.com/video.mp4) (accepts both 'video' and 'Video' for compatibility)
        Caption:
          type: string
          example: my video
          description: Optional caption for the video (accepts both 'caption' and 'Caption' for compatibility)
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID (accepts both 'id' and 'Id' for compatibility)
        MimeType:
          type: string
          example: video/mp4
          description: Optional MIME type override (video/mp4, video/3gpp, video/quicktime, video/x-ms-asf) (accepts both 'mimetype' and 'MimeType' for compatibility)
        JPEGThumbnail:
          type: string
          format: byte
          description: "Optional JPEG thumbnail as plain base64 (no data: prefix). Generated from the video when omitted. Accepts `jpeg_thumbnail` or `JPEGThumbnail`."
          example: /9j/4AAQSkZJRgABAQEASABIAAD/wAARCAABAAEBAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/aAAgBAQAAPwAA/9k=
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator
        ViewOnce:
          type: boolean
          example: true
          description: Set to true for disappearing message after viewing (accepts both 'view_once' and 'ViewOnce' for compatibility)
        Duration:
          type: integer
          example: 86400
          description: Message expiration time in seconds (accepts both 'duration' and 'Duration' for compatibility)
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessagePTV:
      type: object
      required:
        - Phone
        - Video
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code (accepts both 'phone' and 'Phone' for compatibility)
        Video:
          type: string
          example: data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDE=
          description: Video data as base64 data URL (data:video/mp4;base64,xxx) or HTTP(S) URL (https://example.com/video.mp4) for PTV message (accepts both 'video' and 'Video' for compatibility)
    MessageEvent:
      type: object
      required:
        - Phone
        - Name
        - StartTime
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code. Must include country code without + symbol (e.g., 5511999999999).
        Name:
          type: string
          example: Team Meeting
          description: Event title/name. Maximum 255 characters.
        Description:
          type: string
          example: Weekly team sync meeting to discuss project updates
          description: Optional detailed event description. Maximum 1024 characters. Supports Unicode and emojis.
        StartTime:
          type: integer
          format: int64
          example: 2524608000
          description: Event start time as Unix timestamp (seconds since epoch). Required field.
        EndTime:
          type: integer
          format: int64
          example: 2556143999
          description: Event end time as Unix timestamp (seconds since epoch). Optional, but recommended.
        ExtraGuestsAllowed:
          type: boolean
          example: true
          description: Set to true to allow participants to invite additional guests to the event.
        IsCanceled:
          type: boolean
          example: false
          description: "Set to true to mark the event as cancelled. Default: false."
        IsScheduleCall:
          type: boolean
          example: true
          description: Set to true if this is a scheduled call event (video/voice meeting).
        Id:
          type: string
          example: EVENT_001
          description: Optional custom message ID. If not provided, a random one will be generated.
        Presence:
          type: integer
          example: 2000
          description: Milliseconds to simulate typing indicator before sending event.
        NumberCheck:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        Location:
          type: object
          description: Optional event location details with GPS coordinates.
          properties:
            Name:
              type: string
              example: Conference Room A, Main Office
              description: Location name or address. Maximum 255 characters.
            DegreesLatitude:
              type: number
              format: float
              example: -23.5505
              description: Latitude coordinate for the location (GPS).
            DegreesLongitude:
              type: number
              format: float
              example: -46.6333
              description: Longitude coordinate for the location (GPS).
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    ButtonsMessage:
      type: object
      description: Interactive message with buttons.
      required:
        - phone
        - title
        - buttons
      properties:
        phone:
          type: string
          example: "5511999999999"
          description: Recipient phone number with country code (without +) or group JID (e.g., 120363000000000011@g.us).
        title:
          type: string
          minLength: 1
          maxLength: 60
          example: PIX payment
          description: Required heading displayed above the body. Must contain 1 to 60 characters. Do not send a whitespace-only value because WhatsApp will render an empty header area.
        body:
          type: string
          example: Scan the QR code to pay with PIX
          description: Primary message text. Use this or text when not sending media headers.
        text:
          type: string
          example: Choose an option below
          description: Alternative field for the primary text. When media headers are present, prefer caption/text over body.
        footer:
          type: string
          example: ZuckZapGo team
          description: Optional footer text displayed below the buttons.
        caption:
          type: string
          example: Deal of the week
          description: Optional media caption when using image/video/document headers.
        image:
          type: object
          description: Optional image header.
          required:
            - url
          properties:
            url:
              type: string
              format: uri
              example: https://picsum.photos/600/400.jpg
              description: HTTP(S) or data URL pointing to the image.
        video:
          type: object
          description: Optional video header.
          required:
            - url
          properties:
            url:
              type: string
              format: uri
              example: https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
              description: HTTP(S) or data URL pointing to the video file.
        document:
          type: object
          description: Optional document header (PDF, DOC, etc.).
          required:
            - url
          properties:
            url:
              type: string
              format: uri
              example: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
              description: HTTP(S) or data URL pointing to the document.
        buttons:
          type: array
          description: 1 to 3 interactive buttons (`400 3 buttons is the limit` above 3). A reply/quick_reply button cannot be mixed with other button types.
          minItems: 1
          maxItems: 3
          items:
            type: object
            required:
              - buttonId
              - buttonText
              - type
            properties:
              buttonId:
                type: string
                example: pix_btn_1
                description: Unique button identifier returned in callbacks.
              buttonText:
                type: object
                description: Label configuration for the button.
                required:
                  - displayText
                properties:
                  displayText:
                    type: string
                    example: Pay with PIX
                    description: Button label shown to end users, 1-30 characters (`400 text can´t be null` / `text max 30 characters`).
                    maxLength: 30
              type:
                type: string
                enum:
                  - quick_reply
                  - reply
                  - url
                  - cta_url
                  - call
                  - cta_call
                  - copy
                  - cta_copy
                  - pix
                  - pix_payment
                  - payment_info
                  - review_and_pay
                example: pix
                description: Button action type. Use `pix`/`payment_info` for PIX payments, `review_and_pay` for order payments, and quick_reply/cta_* for interactive actions.
              url:
                type: string
                format: uri
                example: https://example.com/offer
                description: Destination URL for url/cta_url buttons.
              merchant_url:
                type: string
                format: uri
                example: https://store.example.com/checkout
                description: Alternative merchant URL for url buttons.
              phone:
                type: string
                example: "+5511988888888"
                description: Phone number in E.164 format for call/cta_call buttons.
              code:
                type: string
                example: COUPON123
                description: Coupon or code copied when using copy/cta_copy buttons.
              pix_key:
                type: string
                example: "11999999999"
                description: PIX key for pix/payment_info/review_and_pay buttons.
              merchant_name:
                type: string
                example: Example Store
                description: Merchant name shown on PIX/review_and_pay buttons.
              pix_type:
                type: string
                example: PHONE
                enum:
                  - PHONE
                  - EMAIL
                  - CPF
                  - CNPJ
                  - EVP
                description: Type of PIX key provided in pix_key.
              currency:
                type: string
                example: BRL
                description: Currency code (ISO 4217) for review_and_pay buttons.
              total_value:
                type: integer
                example: 2599
                description: Total amount in minor units (e.g., cents) for review_and_pay buttons.
              total_offset:
                type: integer
                example: 100
                description: Optional offset or discount amount in minor units.
              reference_id:
                type: string
                example: ORDER-2024-001
                description: Reference identifier associated with the payment/order.
              items:
                type: array
                description: Detailed order items for review_and_pay buttons.
                items:
                  type: object
                  required:
                    - name
                    - quantity
                    - amount_value
                    - amount_offset
                  properties:
                    name:
                      type: string
                      example: Headphones
                      description: Item name.
                    quantity:
                      type: integer
                      example: 1
                      description: Quantity of the item.
                    amount_value:
                      type: integer
                      example: 7990
                      description: Item value in minor units (e.g., cents).
                    amount_offset:
                      type: integer
                      example: 100
                      description: Optional discount/offset in minor units for the item.
        id:
          type: string
          example: 3EB0C767D26A1D6B8A9F
          description: Optional custom message ID. If not provided, a random one will be generated.
        presence:
          type: integer
          example: 2000
          description: Milliseconds to simulate typing indicator before sending the message.
        duration:
          type: integer
          example: 10
          description: Ephemeral duration for the message (when applicable).
        number_check:
          type: boolean
          example: true
          description: Set to true to verify if the phone number is registered on WhatsApp before sending.
        context_info:
          $ref: "#/components/schemas/ContextInfo"
    FlowMessage:
      type: object
      description: Native flow interactive message.
      required:
        - Phone
        - Buttons
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Recipient phone number with country code (without +) or group JID.
        Body:
          type: string
          example: Complete your profile to unlock premium features
          description: Primary message text. Use this or Text/Message.
        Text:
          type: string
          example: Complete your profile to unlock premium features
          description: Alternative body text.
        Message:
          type: string
          example: Complete your profile to unlock premium features
          description: Legacy body text field.
        Caption:
          type: string
          example: Let's get started
          description: Optional caption when using media headers.
        Footer:
          type: string
          example: Powered by ZuckZapGo
          description: Optional footer text.
        Id:
          type: string
          example: FLOW_MSG_001
          description: Custom message ID. Auto-generated when omitted.
        Presence:
          type: integer
          example: 2000
          description: Typing simulation in milliseconds before sending.
        NumberCheck:
          type: boolean
          example: true
          description: Verify that the recipient is registered on WhatsApp before sending.
        MessageVersion:
          type: integer
          format: int32
          example: 3
          description: Native flow message version. Use the latest available version.
        MessageParams:
          type: object
          description: Flow parameters as an object. Use MessageParams or MessageParamsJSON.
          additionalProperties: true
          example:
            flow_type: registration
            required_fields:
              - name
              - email
        MessageParamsJSON:
          type: string
          example: '{"flow_type":"registration"}'
          description: Flow parameters represented as a JSON string.
        Header:
          type: object
          description: Optional header with title, subtitle, and media.
          properties:
            Title:
              type: string
              example: Complete your profile
              description: Header title.
            Subtitle:
              type: string
              example: Only takes 2 minutes
              description: Header subtitle.
            Media:
              type: object
              properties:
                Type:
                  type: string
                  enum:
                    - image
                    - video
                    - document
                  example: image
                  description: Header media type.
                Url:
                  type: string
                  example: https://example.com/header.png
                  description: Media URL or base64 data URL.
                Caption:
                  type: string
                  example: Preview
                  description: Media caption.
                Filename:
                  type: string
                  example: guide.pdf
                  description: File name for document media.
                MimeType:
                  type: string
                  example: image/png
                  description: MIME type of the media.
              description: Optional header media.
        Buttons:
          type: array
          description: Array of native flow buttons.
          minItems: 1
          items:
            type: object
            required:
              - Name
            properties:
              Name:
                type: string
                example: complete_profile
                description: Button identifier.
              ButtonParams:
                type: object
                description: Button parameters as an object.
                additionalProperties: true
                example:
                  display_text: Complete Profile
                  flow_id: "123456789"
              ButtonParamsJSON:
                type: string
                example: '{"display_text":"Complete Profile","flow_id":"123456789"}'
                description: Button parameters as JSON string.
              Params:
                type: object
                description: Optional flow parameters object.
                additionalProperties: true
                example:
                  mode: draft
                  version: "3.0"
              ParamsJSON:
                type: string
                example: '{"mode":"published","version":"3.0"}'
                description: Optional flow parameters JSON string.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    CarouselMessage:
      type: object
      description: Carousel message with multiple cards.
      required:
        - Phone
        - Message
        - Carousel
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Recipient phone number with country code (without +) or group JID.
        Message:
          type: string
          example: Check out our featured products!
          description: Introductory text shown before the carousel cards.
        Carousel:
          type: array
          description: Array of carousel card objects.
          minItems: 1
          maxItems: 10
          items:
            type: object
            required:
              - Text
              - Buttons
            properties:
              Text:
                type: string
                example: |-
                  Premium Wireless Headphones

                  • Noise cancellation
                  • 30h battery
                  • Premium sound

                  $299.99
                description: Card body text.
              MediaUrl:
                type: string
                example: https://picsum.photos/600/400.jpg
                description: Optional media URL or base64 data URL.
              MediaType:
                type: string
                enum:
                  - image
                  - video
                  - document
                example: image
                description: Media type required when MediaUrl is provided.
              Filename:
                type: string
                example: Product_Guide.pdf
                description: Optional filename for document media.
              Caption:
                type: string
                example: Best Seller!
                description: Optional media caption.
              Buttons:
                type: array
                description: Card-specific buttons.
                minItems: 1
                maxItems: 3
                items:
                  type: object
                  required:
                    - Label
                    - Type
                  properties:
                    Id:
                      type: string
                      example: buy_headphones
                      description: Button identifier for reply/copy buttons.
                    Label:
                      type: string
                      example: Buy Now
                      description: Button display text.
                    Url:
                      type: string
                      example: https://store.example.com/product/123
                      description: Destination URL for url buttons.
                    Type:
                      type: string
                      enum:
                        - reply
                        - url
                        - copy
                        - call
                      example: url
                      description: Button type.
        Id:
          type: string
          example: CAROUSEL_MSG_001
          description: Custom message ID. Auto-generated when omitted.
        Presence:
          type: integer
          example: 2000
          description: Typing simulation in milliseconds before sending.
        NumberCheck:
          type: boolean
          example: true
          description: Verify that the recipient is registered on WhatsApp before sending.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageSticker:
      type: object
      required:
        - Phone
        - Sticker
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code
        Sticker:
          type: string
          example: data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=
          description: Sticker data as base64 data URL (data:image/webp;base64,xxx) or HTTP(S) URL (https://example.com/sticker.webp)
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID
        MimeType:
          type: string
          example: image/webp
          description: Optional MIME type override (image/webp, image/jpeg, image/png, image/gif)
        PngThumbnail:
          type: string
          format: byte
          description: Optional PNG thumbnail as base64 string
          example: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    MessageDocument:
      type: object
      required:
        - Phone
        - Document
        - FileName
      properties:
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code
        Document:
          type: string
          example: data:application/pdf;base64,JVBERi0xLjQKJcOkw7zDtsO...
          description: Document data as base64 data URL (data:application/pdf;base64,xxx) or HTTP(S) URL (https://example.com/document.pdf)
        FileName:
          type: string
          example: document.pdf
          description: Name of the document file with extension
        Caption:
          type: string
          example: Important document
          description: Optional caption for the document
        Id:
          type: string
          example: ABCDABCD1234
          description: Optional custom message ID
        MimeType:
          type: string
          example: application/pdf
          description: Optional MIME type override (application/pdf, application/msword, text/plain, etc.)
        Presence:
          type: integer
          example: 3000
          description: Milliseconds to simulate typing indicator
        Duration:
          type: integer
          example: 86400
          description: Message expiration time in seconds
        EchoApi:
          type: boolean
          example: true
          description: Optional. Set to true to force emission of API echo events for this specific message, regardless of global or per-session echo settings. When enabled, a synthetic Message event with Info.IsFromAPI=true will be emitted through your configured transports/webhook after the message is successfully sent. This allows individual message-level control over echo behavior, overriding both ECHO_API_MESSAGES_ENABLED (global) and the per-session echo_api_messages setting. Useful for selectively tracking specific API-sent messages through your event pipeline.
        ContextInfo:
          $ref: "#/components/schemas/ContextInfo"
    DeleteMessage:
      type: object
      required:
        - Id
        - Phone
      properties:
        Phone:
          type: string
          description: Chat JID — a phone number for 1:1 or the group JID (…@g.us).
          example: "5511999999999"
        Id:
          type: string
          example: 3EB0C767D26A1D6B8A9F
          description: "ID of the message to revoke: the `Id` returned when it was sent, or `event.Info.ID` from the webhook."
        Sender:
          type: string
          description: |
            Optional. Original author's JID for a GROUP ADMIN revoke of someone
            else's message (phone JID or @lid). Omit to delete your own message.
          example: 5521999999999@s.whatsapp.net
    Markread:
      type: object
      required:
        - Id
        - Chat
      properties:
        Id:
          type: array
          example:
            - 3EB0C767D26A1D6B8A9F
            - 3EB0A1B2C3D4E5F60718
          items:
            type: string
          description: Message IDs to mark as read (`event.Info.ID`). All IDs must be from the same sender.
        Chat:
          type: string
          example: 5511999999999@s.whatsapp.net
          description: Full chat JID as received in `event.Info.Chat` (`@s.whatsapp.net`, `@lid`, `@g.us`, `@newsletter`). A bare phone number is not converted.
        Sender:
          type: string
          example: 123456789012345@lid
          description: Author JID (`event.Info.Sender`). Required in groups; ignored in 1:1 chats.
    Pairphone:
      type: object
      description: Phone number and device label for code pairing (`POST /session/pairphone`, `POST /connector/pairphone`).
      required:
        - Phone
      properties:
        Phone:
          type: string
          description: Phone number with country code, digits only (no `+`).
          example: "5511999999999"
        client_type:
          type: string
          description: Device label shown on the phone next to the linking code. Case-insensitive; omitted or empty means `chrome` ("Chrome (Linux)").
          enum:
            - unknown
            - chrome
            - edge
            - firefox
            - ie
            - opera
            - safari
            - electron
            - uwp
            - otherwebclient
            - macos
            - android
          example: chrome
    StickerPack:
      type: object
      description: Public sticker pack as returned by `GET /business/sticker-pack/{packId}` (inside the gateway envelope `data`).
      properties:
        success:
          type: boolean
          description: Always true on success.
          example: true
        id:
          type: string
          description: Pack ID.
          example: 8e2a3c1f4b5d6e7f8a9b0c1d2e3f4a5b
        name:
          type: string
          description: Pack name.
          example: Cute Cats
        publisher:
          type: string
          description: Publisher name.
          example: Acme Stickers
        description:
          type: string
          description: Pack description.
          example: A pack of expressive cat stickers.
        file_size:
          type: string
          description: Total pack size in bytes, as a string.
          example: "1238423"
        image_data_hash:
          type: string
          description: Pack image data hash.
          example: f31a4c9e8d7b6a5f4e3d2c1b0a9f8e7d
        animated:
          type: integer
          description: 1 when the pack is animated, otherwise 0.
          example: 0
        lottie:
          type: integer
          description: 1 when the pack uses Lottie stickers, otherwise 0.
          example: 0
        preview_image_ids:
          type: array
          description: Preview image IDs.
          items:
            type: string
          example:
            - 1.webp
            - 2.webp
        tray_image_id:
          type: string
          description: Tray icon image ID.
          example: tray.png
        tray_image_preview:
          type: string
          description: Base64 tray icon preview.
          example: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
        stickers:
          type: array
          description: Stickers of the pack.
          items:
            $ref: "#/components/schemas/StickerPackItem"
        sticker_count:
          type: integer
          description: Number of stickers returned.
          example: 1
    StickerPackItem:
      type: object
      description: One sticker with the fields required by `/chat/downloadsticker`.
      properties:
        url:
          type: string
          description: Media URL.
          example: https://mmg.whatsapp.net/v/t62.15575-24/example-sticker.webp
        direct_path:
          type: string
          description: Media direct path.
          example: /v/t62.15575-24/example-sticker.webp
        media_key:
          type: string
          description: Media key (hex).
          example: 5f1d0c2b9a8e7f6d5c4b3a2918070605f4e3d2c1b0a998877665544332211000
        file_hash:
          type: string
          description: SHA-256 of the plaintext file (hex).
          example: a3f1c2d4e5b60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90
        file_enc_hash:
          type: string
          description: SHA-256 of the encrypted file (hex).
          example: b4e2d3c5f6a70819203b4c5d6e7f8091b2c3d4e5f6a70819203b4c5d6e7f8091
        file_size:
          type: integer
          description: File size in bytes.
          example: 24576
        mimetype:
          type: string
          description: MIME type.
          example: image/webp
        width:
          type: integer
          description: Width in pixels.
          example: 512
        height:
          type: integer
          description: Height in pixels.
          example: 512
        emojis:
          type: array
          description: Emojis associated with the sticker.
          items:
            type: string
          example:
            - 😺
        accessibility_text:
          type: string
          description: Accessibility label.
          example: Smiling orange cat
        handle:
          type: string
          description: Sticker handle inside the pack.
          example: "01"
        sticker_hash_without_meta:
          type: string
          description: Sticker hash without metadata (hex).
          example: c5f3e4d6a7b8091a2b3c4d5e6f708192c3d4e5f6a7b8091a2b3c4d5e6f708192
        preview_webp_id:
          type: string
          description: Preview image ID.
          example: 1_preview.webp
    Checkuser:
      type: object
      description: Phone numbers to check.
      required:
        - Phone
      properties:
        Phone:
          type: array
          description: Phone numbers in international format, without `+`. Must be an array with at least one item.
          minItems: 1
          items:
            type: string
            description: Phone number with country code.
            example: "5511999999999"
          example:
            - "5511999999999"
            - "5511988888888"
    Checkavatar:
      type: object
      description: Account whose profile picture is requested, plus optional access context.
      required:
        - Phone
      properties:
        Phone:
          type: string
          description: Phone number, user JID, group JID or community JID.
          example: "5511999999999"
        Preview:
          type: boolean
          default: false
          description: "`true` returns the thumbnail, `false` the full-resolution picture."
          example: true
        IsCommunity:
          type: boolean
          default: false
          description: Set to `true` when `Phone` is a community JID.
          example: false
        CommonGID:
          type: string
          description: Group JID shared with the account, used as access context for privacy-restricted pictures.
          example: 120363000000000011@g.us
        InviteCode:
          type: string
          description: Group invite code, to read a group picture before joining.
          example: AbCdEfGhIjKlMnOpQrStUv
        PersonaID:
          type: string
          description: Meta AI persona ID, to read a bot persona picture.
          example: "867051314767696"
    Webhook:
      type: object
      required:
        - WebhookURL
      properties:
        WebhookURL:
          type: string
          example: http://server/webhook
    WebhookSet:
      type: object
      description: Individual webhook URL and optional event subscription.
      properties:
        webhookurl:
          type: string
          description: URL that receives the events, stored without validation. Empty or omitted clears the webhook.
          example: https://example.com/webhooks/whatsapp
        events:
          type: array
          description: "Event types to subscribe to (case-sensitive, see `GET /webhook` for the full list, `All` for every type). Unknown names are discarded. When omitted or empty, the stored subscription is kept."
          items:
            type: string
            description: Canonical event type name.
            example: Message
          example:
            - Message
            - ReadReceipt
            - Connected
    WebhookUpdate:
      type: object
      description: Individual webhook URL, event subscription and activation switch.
      properties:
        webhookurl:
          type: string
          description: URL that receives the events, stored without validation. Ignored (stored empty) when `active` is false.
          example: https://example.com/webhooks/whatsapp
        events:
          type: array
          description: "Event types to subscribe to (trimmed, case-sensitive, see `GET /webhook`). Unknown names are discarded. When omitted or empty, the stored subscription is kept."
          items:
            type: string
            description: Canonical event type name.
            example: Message
          example:
            - Message
            - ReadReceipt
        active:
          type: boolean
          default: false
          description: "`true` keeps the webhook active. Omitted or `false` clears the stored URL (and the subscription when `events` is sent)."
          example: true
    GroupLeave:
      type: object
      description: Group to leave. JSON keys are matched case-insensitively.
      required:
        - GroupJID
      properties:
        GroupJID:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
    GroupName:
      type: object
      description: Group and its new name. JSON keys are matched case-insensitively.
      required:
        - GroupJID
        - Name
      properties:
        GroupJID:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        Name:
          type: string
          minLength: 1
          description: New group subject. Must not be empty; WhatsApp enforces the maximum length.
          example: Customer Success Team
    GroupTopic:
      type: object
      description: Group and its new description. JSON keys are matched case-insensitively.
      required:
        - GroupJID
        - Topic
      properties:
        GroupJID:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        Topic:
          type: string
          minLength: 1
          description: New group description. Must not be empty; WhatsApp enforces the maximum length.
          example: "Support hours: Mon-Fri, 9am-6pm."
    GroupAnnounce:
      type: object
      description: Group and its announce setting. JSON keys are matched case-insensitively.
      required:
        - GroupJID
      properties:
        GroupJID:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        Announce:
          type: boolean
          default: false
          description: "`true` lets only admins send messages; `false` (or omitted) lets every member send."
          example: true
    GroupJoin:
      type: object
      description: Invite code used to join a group. JSON keys are matched case-insensitively.
      required:
        - Code
      properties:
        Code:
          type: string
          description: Invite code, or the full `https://chat.whatsapp.com/<code>` link.
          example: AbCdEfGhIjKlMnOpQrStUv
    GroupInviteInfo:
      type: object
      description: Invite code of the group to preview. JSON keys are matched case-insensitively.
      required:
        - Code
      properties:
        Code:
          type: string
          description: Invite code, or the full `https://chat.whatsapp.com/<code>` link.
          example: AbCdEfGhIjKlMnOpQrStUv
    UpdateGroupParticipants:
      type: object
      description: Group, people and the action to apply. JSON keys are matched case-insensitively; `Action` values are not.
      required:
        - GroupJID
        - Phone
        - Action
      properties:
        GroupJID:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        Phone:
          type: array
          minItems: 1
          description: People to change. Phone numbers with country code or user JIDs (`@s.whatsapp.net` / `@lid`).
          items:
            type: string
            description: Phone number or user JID.
            example: "5511999999999"
          example:
            - "5511999999999"
            - "5511988888888"
        Action:
          type: string
          description: Action applied to every entry in `Phone`.
          enum:
            - add
            - remove
            - promote
            - demote
          example: add
    CreateGroup:
      type: object
      description: Name and initial participants of a new group. JSON keys are matched case-insensitively.
      required:
        - Name
        - Participants
      properties:
        Name:
          type: string
          minLength: 1
          description: Group subject. Must not be empty.
          example: Project Team
        Participants:
          type: array
          minItems: 1
          description: People to add. Phone numbers with country code (no `+`) or user JIDs (`@s.whatsapp.net` / `@lid`).
          items:
            type: string
            description: Phone number or user JID.
            example: "5511999999999"
          example:
            - "5511999999999"
            - "5511988888888"
    GroupLocked:
      type: object
      description: Group and its locked setting. JSON keys are matched case-insensitively.
      required:
        - groupjid
      properties:
        groupjid:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        locked:
          type: boolean
          default: false
          description: "`true` lets only admins edit group info; `false` (or omitted) lets every member edit it."
          example: true
    GroupEphemeral:
      type: object
      description: Group and its disappearing messages timer. JSON keys are matched case-insensitively.
      required:
        - groupjid
        - duration
      properties:
        groupjid:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
        duration:
          type: string
          description: Timer for new messages. `off` disables disappearing messages.
          enum:
            - 24h
            - 7d
            - 90d
            - "off"
          example: 7d
    GroupMemberAddMode:
      type: object
      description: Group and the new "Add other members" permission.
      required:
        - groupjid
        - mode
      properties:
        groupjid:
          type: string
          description: Group JID (`@g.us`). Key is case-insensitive.
          example: "120363000000000011@g.us"
        mode:
          type: string
          description: "`admin_add` lets only admins add members; `all_member_add` lets every member add members."
          enum:
            - admin_add
            - all_member_add
          example: admin_add
    GroupJoinApproval:
      type: object
      description: Group and whether new members need admin approval.
      required:
        - groupjid
      properties:
        groupjid:
          type: string
          description: Group JID (`@g.us`). Key is case-insensitive.
          example: "120363000000000011@g.us"
        require_approval:
          type: boolean
          default: false
          description: "`true` makes new members wait for admin approval; `false` (or omitted) lets them join directly."
          example: true
    RemoveGroupPhoto:
      type: object
      description: Group whose photo is removed. JSON keys are matched case-insensitively.
      required:
        - groupjid
      properties:
        groupjid:
          type: string
          description: Group JID (`@g.us`).
          example: "120363000000000011@g.us"
    SessionStatus:
      type: object
      description: Connection, pairing and configuration snapshot of the instance returned by `GET /session/status`.
      required:
        - id
        - connected
        - loggedIn
        - hasClient
        - connectionHealth
        - timestamp
      properties:
        id:
          type: string
          description: Instance (user) ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        name:
          type: string
          description: Instance name.
          example: Support Line
        avatar_url:
          type: string
          description: Profile picture URL from the 24-hour cache; empty when unknown.
          example: https://pps.whatsapp.net/v/t61.24694-24/example_avatar.jpg
        connected:
          type: boolean
          description: Whether the WhatsApp socket is connected.
          example: true
        loggedIn:
          type: boolean
          description: Whether the device is paired and authenticated.
          example: true
        hasClient:
          type: boolean
          description: Whether a client exists in memory for the instance.
          example: true
        connectionHealth:
          type: string
          description: Summary derived from `hasClient`, `connected` and `loggedIn`.
          enum:
            - no_client
            - disconnected
            - connected_not_logged
            - connected
          example: connected
        lastSuccessfulConnect:
          type: integer
          format: int64
          description: Unix time of the last successful connection; 0 without a client.
          example: 1757862000
        autoReconnectErrors:
          type: integer
          description: Consecutive failed automatic reconnect attempts.
          example: 0
        enableAutoReconnect:
          type: boolean
          description: Whether automatic reconnect is enabled on the client.
          example: true
        expiration:
          type: integer
          format: int64
          description: Seconds until the stored expiration timestamp (negative when already past or unset).
          example: 86400
        token:
          type: string
          description: Instance token. Removed on `GET /connector/status`.
          example: a1b2c3d4e5f6a7b8c9d0
        jid:
          type: string
          description: Device JID; empty before pairing.
          example: 5511999999999:12@s.whatsapp.net
        webhook:
          type: string
          description: Individual webhook URL. Removed on `GET /connector/status`.
          example: https://example.com/webhook
        events:
          type: string
          description: Comma-separated individual webhook subscription. Removed on `GET /connector/status`.
          example: Message,ReadReceipt
        proxy_url:
          type: string
          description: Legacy proxy URL from the user cache. Removed on `GET /connector/status`.
          example: ""
        qrcode:
          type: string
          description: Pending QR code as a PNG data URL; empty when none is pending.
          example: ""
        skip_media_download:
          type: boolean
          description: Media is not downloaded for events.
          example: false
        skip_groups:
          type: boolean
          description: Group events are skipped.
          example: false
        skip_newsletters:
          type: boolean
          description: Newsletter events are skipped.
          example: false
        skip_broadcasts:
          type: boolean
          description: Broadcast and status events are skipped.
          example: true
        skip_own_messages:
          type: boolean
          description: Messages sent by this account are skipped.
          example: false
        echo_api_messages:
          type: boolean
          description: Messages sent through the API are echoed to transports.
          example: false
        skip_calls:
          type: boolean
          description: Call events are skipped (and calls are rejected when configured).
          example: false
        call_reject_message:
          type: string
          description: Text sent when a call is rejected automatically.
          example: Sorry, I cannot take calls at the moment.
        call_reject_type:
          type: string
          description: Automatic call rejection mode as stored.
          example: busy
        globalTransportSkips:
          $ref: "#/components/schemas/SessionStatusTransportSkips"
        isFromAPI:
          type: boolean
          description: Same value as `echo_api_messages`.
          example: false
        auto_presence_unavailable:
          type: boolean
          description: Whether the client marks itself unavailable automatically after connecting.
          example: false
        proxy_config:
          $ref: "#/components/schemas/ProxyConfig"
        s3_config:
          $ref: "#/components/schemas/SessionStatusS3Config"
        rabbitmq_config:
          $ref: "#/components/schemas/SessionStatusRabbitMQConfig"
        passkeyRequired:
          type: boolean
          description: "`true` while a passkey challenge is stored and not confirmed."
          example: false
        passkeyResponseSubmitted:
          type: boolean
          description: Whether a WebAuthn response was already submitted.
          example: false
        passkeyConfirmed:
          type: boolean
          description: Whether the passkey pairing was confirmed.
          example: false
        passkeyConfirmationCode:
          type: string
          description: Confirmation code to compare with the phone; empty until WhatsApp sends it.
          example: ""
        passkeySkipHandoffUX:
          type: boolean
          description: "`true` when WhatsApp does not require the code comparison (the gateway confirms automatically)."
          example: false
        passkeyError:
          type: string
          description: Last passkey error text; empty when none.
          example: ""
        timestamp:
          type: integer
          format: int64
          description: Unix time when the snapshot was built.
          example: 1757865600
    ProxyConfig:
      type: object
      description: Proxy block of the session status.
      required:
        - enabled
      properties:
        enabled:
          type: boolean
          description: "`true` when a proxy URL is stored."
          example: false
        proxy_url:
          type: string
          description: Stored proxy URL, credentials included; empty when none.
          example: ""
    S3Config:
      type: object
      properties:
        enabled:
          type: boolean
          description: Whether S3 storage is enabled
          example: true
        endpoint:
          type: string
          description: S3 endpoint URL (leave empty for AWS S3)
          example: https://s3.amazonaws.com
        region:
          type: string
          description: S3 region
          example: us-east-1
        bucket:
          type: string
          description: S3 bucket name
          example: my-whatsapp-media
        access_key:
          type: string
          description: S3 access key ID
          example: AKIAIOSFODNN7EXAMPLE
        secret_key:
          type: string
          description: S3 secret access key
          example: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        path_style:
          type: boolean
          description: Use path-style URLs (required for MinIO)
          example: false
        public_url:
          type: string
          description: Custom public URL for accessing files (optional, for CDN)
          example: https://cdn.example.com
        media_delivery:
          type: string
          description: "How media is delivered in events: `base64` inline, `s3` (or its alias `url`, stored as `s3`) as a link, or `both`. Empty or omitted is stored as `base64`."
          enum:
            - base64
            - s3
            - url
            - both
          example: both
        retention_days:
          type: integer
          description: Days to keep uploaded objects (0 means no expiration).
          example: 30
        disable_acl:
          type: boolean
          description: Set to true for AWS S3 buckets with "Bucket owner enforced" Object Ownership
          example: true
    RabbitMQConfig:
      type: object
      properties:
        enabled:
          type: boolean
          description: Whether RabbitMQ publishing is enabled
          example: true
        url:
          type: string
          description: RabbitMQ connection URL in format amqp://username:password@host:port/vhost
          example: amqp://guest:guest@localhost:5672/
        exchange:
          type: string
          description: Exchange name for publishing events
          example: whatsapp.events
        exchange_type:
          type: string
          description: Exchange type for events (required when enabled)
          enum:
            - topic
            - direct
            - fanout
            - headers
          example: topic
        queue:
          type: string
          description: "Queue name pattern with dynamic placeholders. Use {user_id} and {event_type} for automatic queue creation per event type. Examples: 'user_{user_id}_main' (static) or 'user_{user_id}_event_{event_type}' (dynamic per event)"
          example: user_{user_id}_event_{event_type}
        queue_type:
          type: string
          description: Queue type for events (required when enabled)
          enum:
            - classic
            - quorum
            - stream
          example: classic
        routing_key:
          type: string
          description: "Routing key pattern with dynamic placeholders. Use {user_id} and {event_type} for event-specific routing. Example: 'whatsapp.{user_id}.{event_type}' creates keys like 'whatsapp.abc123.Message'"
          example: whatsapp.{user_id}.{event_type}
        events:
          type: string
          description: 'Event types to publish (comma-separated or "All"). Available events include all 47 event types from Message, UndecryptableMessage, Receipt, MediaRetry, ReadReceipt, GroupInfo, JoinedGroup, Picture, BlocklistChange, Blocklist, Connected, Disconnected, ConnectFailure, KeepAliveRestored, KeepAliveTimeout, LoggedOut, ClientOutdated, TemporaryBan, StreamError, StreamReplaced, PairSuccess, PairError, QR, QRScannedWithoutMultidevice, PrivacySettings, PushNameSetting, UserAbout, AppState, AppStateSyncComplete, HistorySync, OfflineSyncCompleted, OfflineSyncPreview, CallOffer, CallAccept, CallTerminate, CallOfferNotice, CallRelayLatency, Presence, ChatPresence, IdentityChange, CATRefreshError, NewsletterJoin, NewsletterLeave, NewsletterMuteChange, NewsletterLiveUpdate, FBMessage, to All (which subscribes to all events). Event names are case-sensitive; CSV values with spaces are normalized (for example: "Message, Disconnected").'
          example: Message,ReadReceipt,Connected,All
        durable:
          type: boolean
          description: Whether the exchange and queue should be durable
          example: true
        auto_delete:
          type: boolean
          description: Whether the exchange and queue should auto-delete when unused
          example: false
        exclusive:
          type: boolean
          description: Whether the queue should be exclusive to this connection
          example: false
        no_wait:
          type: boolean
          description: Whether to use no-wait flag for faster operations
          example: false
        delivery_mode:
          type: integer
          description: Message delivery mode (1=non-persistent, 2=persistent)
          enum:
            - 1
            - 2
          example: 2
        dead_letter_exchange:
          type: string
          description: Dead Letter Exchange name. Rejected or expired messages will be routed to this exchange.
          example: dlx.whatsapp
        dead_letter_routing_key:
          type: string
          description: Routing key for Dead Letter Exchange. Used when routing rejected/expired messages to DLQ.
          example: dlq.events
        message_ttl:
          type: integer
          format: int64
          description: Message TTL (Time To Live) in milliseconds. Messages older than this will be discarded or sent to DLX if configured.
          example: 86400000
        max_length:
          type: integer
          format: int64
          description: Maximum number of messages in the queue. Overflow behavior depends on x-overflow setting.
          example: 100000
        max_length_bytes:
          type: integer
          format: int64
          description: Maximum total size of messages in the queue in bytes.
          example: 104857600
        queue_arguments:
          type: string
          additionalProperties: true
          description: Custom queue arguments as a JSON-encoded string (a JSON object here fails decoding with 400). Parsed into x-* arguments when queues are declared.
          example: '{"x-overflow":"reject-publish","x-queue-master-locator":"min-masters"}'
        exchange_arguments:
          type: string
          additionalProperties: true
          description: Custom exchange arguments as a JSON-encoded string (a JSON object here fails decoding with 400).
          example: "{}"
        connection_pool_size:
          type: integer
          description: "Size of the connection pool for RabbitMQ connections. Higher values allow more concurrent operations. Default: 50"
          example: 50
        worker_count:
          type: integer
          description: "Number of concurrent workers for message publishing. Default: 100"
          example: 100
        queue_buffer_size:
          type: integer
          description: "In-memory queue buffer size for event batching before publishing. Default: 100000"
          example: 100000
        batch_size:
          type: integer
          description: "Number of events to batch together before publishing. Default: 1000"
          example: 1000
        batch_timeout_ms:
          type: integer
          description: "Maximum time in milliseconds to wait before publishing a partial batch. Default: 100"
          example: 100
        publish_timeout_ms:
          type: integer
          description: "Timeout in milliseconds for individual publish operations. Default: 5000"
          example: 5000
        max_retries:
          type: integer
          description: "Maximum number of retry attempts for failed publish operations. Default: 3"
          example: 3
        retry_delay_ms:
          type: integer
          description: "Delay in milliseconds between retry attempts. Default: 1000"
          example: 1000
    SkipMediaConfig:
      type: object
      description: Per-instance media download skip flag.
      properties:
        enabled:
          type: boolean
          description: "`true` skips media download for message events (metadata only, no base64, no S3 upload); `false` downloads media. Omitted is treated as `false`."
          example: true
    SkipGroupsConfig:
      type: object
      description: Per-instance group events skip flag.
      properties:
        enabled:
          type: boolean
          description: "`true` drops every event from `@g.us` group chats before processing; `false` processes them. Omitted is treated as `false`."
          example: true
    SkipNewslettersConfig:
      type: object
      description: Per-instance newsletter (Channels) events skip flag.
      properties:
        enabled:
          type: boolean
          description: "`true` drops every event from `@newsletter` chats and newsletter follow/mute/live updates; `false` processes them. Omitted is treated as `false`."
          example: true
    SkipBroadcastsConfig:
      type: object
      description: Per-instance broadcast and Status events skip flag.
      properties:
        enabled:
          type: boolean
          description: "`true` drops message, receipt and presence events from `@broadcast` chats, including `status@broadcast`; `false` processes them. Omitted is treated as `false`."
          example: true
    SkipCallsConfig:
      type: object
      description: Per-instance automatic call rejection settings.
      properties:
        enabled:
          type: boolean
          description: "`true` rejects incoming calls automatically and drops call offer events; `false` lets calls ring. Omitted is treated as `false`."
          example: true
        reject_message:
          type: string
          description: Text message sent to the caller after the rejection. Empty or omitted is stored as `Sorry, I cannot take calls at the moment.`
          example: Sorry, I cannot take calls right now. Please send a message.
        reject_type:
          type: string
          description: Stored rejection label. Empty or omitted is stored as `busy`. It does not change the rejection sent to WhatsApp.
          enum:
            - busy
            - declined
            - unavailable
          example: busy
    SkipOwnMessagesConfig:
      type: object
      description: Per-instance own-message events skip flag.
      properties:
        enabled:
          type: boolean
          description: "`true` drops events about messages sent by this account (`IsFromMe`); `false` processes them. Omitted is treated as `false`."
          example: true
    MessageEdit:
      type: object
      required:
        - Id
        - Phone
        - Body
      properties:
        Id:
          type: string
          example: 90B2F8B13FAC8A9CF6B06E99C7834DC5
          description: Message ID to edit. A text message, or an image/video/document whose caption you want to change — the type is detected automatically.
        Phone:
          type: string
          example: "5511999999999"
          description: Phone number with country code
        Body:
          type: string
          example: This is the updated message
          description: "New message content: the new text for a text message, or the new caption for an image/video/document."
        ContextInfo:
          type: object
          description: Optional context info for replies
          properties:
            StanzaId:
              type: string
              example: 3EB06F9067F80BAB89FF
              description: Message ID to reply to
            Participant:
              type: string
              example: 5511999999999@s.whatsapp.net
              description: JID of original message sender
            IsForwarded:
              type: boolean
              example: true
              description: Set to true to mark the message as forwarded. This is the preferred method for forwarding messages.
            MentionedJID:
              type: array
              items:
                type: string
              example:
                - 5511999999999@s.whatsapp.net
                - 5491155553934@s.whatsapp.net
              description: Array of JIDs to mention in the message. Use full WhatsApp JID format (number@s.whatsapp.net).
            MentionAll:
              type: boolean
              example: true
              description: Set to true to mention all group members
    GlobalTestEvent:
      type: object
      description: Synthetic event to dispatch. Every field is optional.
      properties:
        event_type:
          type: string
          default: TestEvent
          description: Event type name used for routing and transport event filters.
          example: Message
        user_id:
          type: string
          default: admin-test
          description: User ID attached to the event. A stored user ID also attaches that user's name and JID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        user_token:
          type: string
          description: Token attached to the event. Defaults to `user_id`.
          example: a1b2c3d4e5f6a7b8c9d0
        data:
          type: object
          additionalProperties: true
          description: Event payload. Defaults to `{test, timestamp, message}`.
          example:
            test: true
            message: Delivery check from the admin panel
    CallRejectRequest:
      type: object
      description: Body of `POST /call/reject/send`.
      required:
        - call_id
        - call_from
        - reject_type
      properties:
        call_id:
          type: string
          description: Call ID from the call offer webhook.
          example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
        call_from:
          type: string
          description: Caller JID exactly as received in the call offer webhook (`@s.whatsapp.net` or `@lid`).
          example: "5511999999999@s.whatsapp.net"
        reject_type:
          type: string
          description: Rejection label. Required on this route (no default); echoed in the response.
          enum:
            - busy
            - declined
            - unavailable
          example: busy
        message:
          type: string
          description: Optional text sent to the caller after the rejection (best effort).
          example: "I'm in a meeting right now, I'll call you back soon."
    GetLIDRequest:
      type: object
      description: Phone number whose LID is requested.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Phone number with country code or phone JID. Must not be a LID.
          example: "5511999999999"
    GetJIDFromLIDRequest:
      type: object
      description: LID whose phone number is requested.
      required:
        - lid
      properties:
        lid:
          type: string
          description: LID with the `@lid` suffix.
          example: 123456789012345@lid
    LIDResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        phone:
          type: string
          description: Original phone/JID provided
          example: 5511999999999@s.whatsapp.net
        lid:
          type: string
          description: Generated LID (Link ID)
          example: 2:abcd1234efgh5678@lid
        created_at:
          type: integer
          format: int64
          description: Unix timestamp when LID was created
          example: 1640995200
        message:
          type: string
          description: Success message
          example: LID retrieved successfully
    JIDFromLIDResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        lid:
          type: string
          description: Original LID provided
          example: 2:abcd1234efgh5678@lid
        phone:
          type: string
          description: Phone/JID corresponding to the LID
          example: 5511999999999@s.whatsapp.net
        created_at:
          type: integer
          format: int64
          description: Unix timestamp when LID was created
          example: 1640995200
        message:
          type: string
          description: Success message
          example: Phone/JID retrieved successfully
    LIDMapping:
      type: object
      properties:
        id:
          type: integer
          description: Unique mapping ID
          example: 1
        phone:
          type: string
          description: Phone number or JID
          example: 5511999999999@s.whatsapp.net
        lid:
          type: string
          description: Corresponding LID (Link ID)
          example: 2:abcd1234efgh5678@lid
        created_at:
          type: integer
          format: int64
          description: Unix timestamp when mapping was created
          example: 1640995200
        last_used:
          type: integer
          format: int64
          description: Unix timestamp when mapping was last used
          example: 1640995500
        usage_count:
          type: integer
          description: Number of times this mapping has been used
          example: 5
    LIDMappingsResponse:
      description: Success envelope of `GET /user/lid/mappings`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Mapping list.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                mappings:
                  type: array
                  nullable: true
                  description: Phone/LID pairs; `null` when none exist.
                  items:
                    type: object
                    description: One mapping.
                    properties:
                      lid:
                        type: string
                        description: Contact LID.
                        example: 123456789012345@lid
                      phone:
                        type: string
                        description: Contact phone JID.
                        example: 5511999999999@s.whatsapp.net
                count:
                  type: integer
                  description: Number of mappings returned.
                  example: 2
                message:
                  type: string
                  description: "`No LID mappings found` when the list is empty; omitted otherwise."
                  example: No LID mappings found
    StatusText:
      type: object
      description: Text status to post.
      required:
        - text
      properties:
        text:
          type: string
          description: Status text. WhatsApp limits it to about 650 characters; the gateway does not check the length.
          example: Store open until 10 PM today!
        background_color:
          type: string
          description: Background color as an ARGB unsigned decimal string. Ignored when it does not parse.
          example: "4280391411"
        text_color:
          type: string
          description: Text color as an ARGB unsigned decimal string. Ignored when it does not parse.
          example: "4294967295"
        font:
          type: integer
          description: "Font index 0-10 (0 SYSTEM, 1 SYSTEM_TEXT, 2 FB_SCRIPT, 6 SYSTEM_BOLD, 7 MORNINGBREEZE_REGULAR, 8 CALISTOGA_REGULAR, 9 EXO2_EXTRABOLD, 10 COURIERPRIME_BOLD). Defaults to 0; out-of-range values are ignored."
          minimum: 0
          maximum: 10
          example: 8
        id:
          type: string
          description: Custom message ID. Generated when omitted.
          example: 3EB0C0FFEE0000000001
        audience:
          type: array
          description: Only these contacts see the status. Individual phone digits (a leading `+` and spaces are accepted), `@s.whatsapp.net` or `@lid` JIDs. Other entries (groups, channels, text) are ignored; an empty or entirely invalid list uses the status privacy setting. Omit to use the status privacy setting.
          items:
            type: string
          example:
            - "5511999999999"
            - "123456789012345@lid"
        exclude_list:
          type: array
          description: Contacts removed from the audience (same formats as `audience`). Invalid entries are ignored. If nothing would remain, the status falls back to the account's status privacy instead of failing.
          items:
            type: string
          example:
            - "5511888888888"
    StatusImage:
      type: object
      description: Image status to post.
      required:
        - image
      properties:
        image:
          type: string
          description: Base64 data URL (`data:image/jpeg;base64,...`) or HTTP(S) URL of the image.
          example: https://example.com/media/weekly-offer.jpg
        caption:
          type: string
          description: Caption shown with the image.
          example: This week's offer
        id:
          type: string
          description: Custom message ID. Generated when omitted.
          example: 3EB0C0FFEE0000000002
        audience:
          type: array
          description: Only these contacts see the status. Individual phone digits (a leading `+` and spaces are accepted), `@s.whatsapp.net` or `@lid` JIDs. Other entries are ignored; an empty or entirely invalid list uses the status privacy setting. Omit to use the status privacy setting.
          items:
            type: string
          example:
            - "5511999999999"
        exclude_list:
          type: array
          description: Contacts removed from the audience. Invalid entries are ignored. If nothing would remain, the status falls back to the account's status privacy instead of failing.
          items:
            type: string
          example:
            - "5511888888888"
    StatusVideo:
      type: object
      description: Video status to post.
      required:
        - video
      properties:
        video:
          type: string
          description: Base64 data URL (`data:video/mp4;base64,...`) or HTTP(S) URL of an MP4 (H.264 video, AAC audio).
          example: https://example.com/media/product-demo.mp4
        caption:
          type: string
          description: Caption shown with the video.
          example: 30-second product demo
        id:
          type: string
          description: Custom message ID. Generated when omitted.
          example: 3EB0C0FFEE0000000003
        audience:
          type: array
          description: Only these contacts see the status. Individual phone digits (a leading `+` and spaces are accepted), `@s.whatsapp.net` or `@lid` JIDs. Other entries are ignored; an empty or entirely invalid list uses the status privacy setting. Omit to use the status privacy setting.
          items:
            type: string
          example:
            - "5511999999999"
        exclude_list:
          type: array
          description: Contacts removed from the audience. Invalid entries are ignored. If nothing would remain, the status falls back to the account's status privacy instead of failing.
          items:
            type: string
          example:
            - "5511888888888"
    StatusAudio:
      type: object
      description: Audio or voice note status to post.
      required:
        - audio
      properties:
        audio:
          type: string
          description: Base64 data URL or HTTP(S) URL. Voice notes should be OGG/Opus; audio files accept MP3, M4A/AAC or OGG.
          example: data:audio/ogg;base64,T2dnUwACAAAAAAAAAAAAAAAAAAAAAMhHv8oBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dT
        caption:
          type: string
          description: Accepted for compatibility but not attached to the audio status.
          example: Listen to this
        ptt:
          type: boolean
          default: true
          description: "`true` posts a voice note (MIME normalized to `audio/ogg; codecs=opus`), `false` posts an audio file."
          example: true
        id:
          type: string
          description: Custom message ID. Generated when omitted.
          example: 3EB0C0FFEE0000000004
        audience:
          type: array
          description: Only these contacts see the status. Individual phone digits (a leading `+` and spaces are accepted), `@s.whatsapp.net` or `@lid` JIDs. Other entries are ignored; an empty or entirely invalid list uses the status privacy setting. Omit to use the status privacy setting.
          items:
            type: string
          example:
            - "5511999999999"
        exclude_list:
          type: array
          description: Contacts removed from the audience. Invalid entries are ignored. If nothing would remain, the status falls back to the account's status privacy instead of failing.
          items:
            type: string
          example:
            - "5511888888888"
    ChangePushNameRequest:
      type: object
      description: New push name.
      required:
        - push_name
      properties:
        push_name:
          type: string
          description: Display name shown to people who have not saved your number. Cannot be empty.
          minLength: 1
          example: Example Store
    SetMyStatusRequest:
      type: object
      description: New status (about) text.
      properties:
        status:
          type: string
          description: About text; WhatsApp accepts up to 139 characters. Empty clears the text.
          maxLength: 139
          example: Available for support 9am-6pm
    SetProfilePhoto:
      type: object
      description: New profile photo.
      required:
        - Image
      properties:
        Image:
          type: string
          description: JPEG image as a base64 data URL (`data:image/jpeg;base64,...`) or an `http(s)` URL to download.
          example: https://example.com/images/profile.jpg
    BusinessProfileRequest:
      type: object
      description: Business account to look up.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Phone number with country code or user JID.
          example: "5511988888888"
    UserInfoImprovedResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        data:
          type: object
          properties:
            jid:
              type: string
              description: User's WhatsApp JID
              example: 5491155553934@s.whatsapp.net
            status:
              type: string
              description: User's current status message
              example: Available for work 💼
            picture_id:
              type: string
              description: Profile picture ID
              example: "1582328807"
            verified_name:
              type: string
              description: Verified business name (if available)
              example: Company Name
            devices:
              type: array
              items:
                type: object
                properties:
                  user:
                    type: string
                    description: User number
                    example: "5491155553934"
                  agent:
                    type: string
                    description: Device agent ID
                    example: "25"
                  device:
                    type: string
                    description: Device platform name
                    example: CHROME
                  server:
                    type: string
                    description: Server domain
                    example: s.whatsapp.net
                  ad:
                    type: string
                    description: Advertisement string
                    example: 5491155553934.0:6@s.whatsapp.net
        success:
          type: boolean
          example: true
      example:
        code: 200
        data:
          jid: 5491155553934@s.whatsapp.net
          status: Available for work 💼
          picture_id: "1582328807"
          verified_name: Company Name
          devices:
            - user: "5491155553934"
              agent: "25"
              device: CHROME
              server: s.whatsapp.net
              ad: 5491155553934.0:6@s.whatsapp.net
            - user: "5491155553934"
              agent: "15"
              device: DESKTOP
              server: s.whatsapp.net
              ad: 5491155553934.0:0@s.whatsapp.net
        success: true
    LinkGroupRequest:
      type: object
      required:
        - parent_jid
        - child_jid
      properties:
        parent_jid:
          type: string
          description: JID of the parent community
          example: 120363000000000101@newsletter
        child_jid:
          type: string
          description: JID of the child group to link
          example: 120363000000000011@g.us
      example:
        parent_jid: 120363000000000101@newsletter
        child_jid: 120363000000000011@g.us
    UnlinkGroupRequest:
      type: object
      required:
        - parent_jid
        - child_jid
      properties:
        parent_jid:
          type: string
          description: JID of the parent community
          example: 120363000000000101@newsletter
        child_jid:
          type: string
          description: JID of the child group to unlink
          example: 120363000000000011@g.us
      example:
        parent_jid: 120363000000000101@newsletter
        child_jid: 120363000000000011@g.us
    LinkedGroupsRequest:
      type: object
      description: Community whose sub-groups are listed. A `community` query parameter overrides this field.
      required:
        - community
      properties:
        community:
          type: string
          description: Community (parent group) JID. Leading and trailing spaces are trimmed.
          example: "120363000000000033@g.us"
    LinkedGroupsResponse:
      description: Success envelope of the linked-groups routes.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Sub-groups of the community.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                community:
                  type: string
                  description: Normalized community JID.
                  example: "120363000000000033@g.us"
                linked_groups:
                  type: array
                  nullable: true
                  description: Sub-groups of the community; `null` when there are none.
                  items:
                    type: object
                    description: One sub-group.
                    properties:
                      jid:
                        type: string
                        description: Sub-group JID.
                        example: "120363000000000034@g.us"
                      name:
                        type: string
                        description: Sub-group name.
                        example: Company Announcements
                      is_default_sub:
                        type: boolean
                        description: "`true` for the community announcement group."
                        example: true
                count:
                  type: integer
                  description: Number of sub-groups.
                  example: 2
    GroupRequestsRequest:
      type: object
      description: Group or community whose pending join requests are listed.
      required:
        - group
      properties:
        group:
          type: string
          description: Group or community JID.
          example: "120363000000000011@g.us"
    GroupRequestsResponse:
      description: Success envelope of `POST /community/requests`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Pending join requests.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                group:
                  type: string
                  description: Normalized group JID.
                  example: "120363000000000011@g.us"
                pending_requests:
                  type: array
                  nullable: true
                  description: People waiting for approval; `null` when nobody is waiting.
                  items:
                    type: object
                    description: One pending request.
                    properties:
                      jid:
                        type: string
                        description: JID of the requester (LID or phone JID).
                        example: 123456789012345@lid
                count:
                  type: integer
                  description: Number of pending requests.
                  example: 2
    UpdateGroupRequestsRequest:
      type: object
      required:
        - group_jid
        - action
        - participants
      properties:
        group_jid:
          type: string
          description: JID of the group
          example: 120363000000000011@g.us
        action:
          type: string
          enum:
            - approve
            - reject
          description: Action to perform on join requests
          example: approve
        participants:
          type: array
          items:
            type: string
          description: Array of participant JIDs to approve/reject
          example:
            - 5491155553934@s.whatsapp.net
            - 5511999999999@s.whatsapp.net
      example:
        group_jid: 120363000000000011@g.us
        action: approve
        participants:
          - 5491155553934@s.whatsapp.net
          - 5511999999999@s.whatsapp.net
    CreateCommunityRequest:
      type: object
      description: Name and optional groups of a new community.
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          description: Community name.
          example: Acme Company
        description:
          type: string
          description: Echoed in the response only; it is not sent to WhatsApp.
          example: Official community of Acme employees
        groups:
          type: array
          description: Existing group JIDs to link after creation. Failures are skipped silently.
          items:
            type: string
            description: Group JID.
            example: "120363000000000011@g.us"
          example:
            - "120363000000000011@g.us"
            - "120363000000000012@g.us"
    CreateCommunityResponse:
      description: Success envelope of `POST /community/create`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Creation result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Community created successfully
                community:
                  type: object
                  description: The new community.
                  properties:
                    jid:
                      type: string
                      description: Community JID assigned by WhatsApp.
                      example: "120363000000000033@g.us"
                    name:
                      type: string
                      description: Name from the request.
                      example: Acme Company
                    description:
                      type: string
                      description: Description from the request (not applied on WhatsApp).
                      example: Official community of Acme employees
                    linked_groups:
                      type: array
                      nullable: true
                      description: Groups actually linked; `null` when none.
                      items:
                        type: string
                        description: Linked group JID.
                        example: "120363000000000011@g.us"
                      example:
                        - "120363000000000011@g.us"
    CommunityAnnouncementRequest:
      type: object
      description: Target JID and announcement text.
      required:
        - community
        - message
      properties:
        community:
          type: string
          description: JID the text is sent to, usually the community announcement group.
          example: "120363000000000034@g.us"
        message:
          type: string
          minLength: 1
          description: Announcement text.
          example: Office closed on Friday for maintenance.
        linked_groups:
          type: array
          description: Accepted but ignored.
          items:
            type: string
            description: Group JID (ignored).
            example: "120363000000000011@g.us"
          example:
            - "120363000000000011@g.us"
        background_color:
          type: string
          description: Accepted but ignored.
          example: "#128C7E"
        font:
          type: integer
          description: Accepted but ignored.
          example: 0
    CommunityAnnouncementResponse:
      description: Success envelope of `POST /community/announcement`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Send result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Announcement sent successfully
                community:
                  type: string
                  description: Normalized target JID.
                  example: "120363000000000034@g.us"
                message_id:
                  type: string
                  description: WhatsApp message ID of the text.
                  example: 3EB0C767D26A1D6B8A9F
                timestamp:
                  type: string
                  format: date-time
                  description: Server timestamp of the send.
                  example: "2026-09-15T12:00:00Z"
    ResolveBusinessLinkRequest:
      type: object
      description: Business message link to resolve.
      required:
        - link
      properties:
        link:
          type: string
          description: WhatsApp business message link or its code (`https://wa.me/message/<code>`). Required (`400 missing link in payload`).
          example: https://wa.me/message/ABCDEFGHIJKLM1
      example:
        link: https://wa.me/message/ABCDEFGHIJKLM1
    ContactQRRequest:
      type: object
      description: Options for the own contact QR link. The body must be a JSON object (use `{}` for defaults).
      properties:
        revoked:
          type: boolean
          description: True revokes the current contact QR link and returns a new one; false (default) returns the current link.
          default: false
          example: false
        phone:
          type: string
          description: "Accepted for backward compatibility but ignored: the route always returns the link of the connected account."
          example: "5511999999999"
      example:
        revoked: false
    BotProfilesRequest:
      type: object
      description: Bots to describe.
      required:
        - bot_jids
      properties:
        bot_jids:
          type: array
          description: Bot JIDs from `GET /business/bots` (at least one). Each must parse as a JID; JIDs not in the bot list are ignored.
          items:
            type: string
          minItems: 1
          example:
            - 867051314767696@bot
      example:
        bot_jids:
          - 867051314767696@bot
    UserDevicesRequest:
      type: object
      description: Accounts whose devices are listed.
      required:
        - user_jids
      properties:
        user_jids:
          type: array
          description: Phone numbers with country code, phone JIDs or LIDs. At least one item.
          minItems: 1
          items:
            type: string
            description: Phone number, phone JID or LID.
            example: 5511999999999@s.whatsapp.net
          example:
            - 5511999999999@s.whatsapp.net
            - "5511988888888"
    DeviceInfo:
      type: object
      description: One device of a queried account.
      properties:
        user_jid:
          type: string
          description: Account JID the device belongs to (`user@server`).
          example: 5511999999999@s.whatsapp.net
        device_jid:
          type: string
          description: Device JID (`user:device@server`; slot 0 has no suffix).
          example: 5511999999999:7@s.whatsapp.net
        device_id:
          type: integer
          description: Device slot number; 0 is the primary phone.
          example: 7
        platform:
          type: string
          description: Label derived from `device_id` (see `POST /device/platform`), not the real client platform.
          example: DESKTOP
    LinkedDevicesResponse:
      description: Success envelope of `GET /device/linked`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Devices of the connected account.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                jid:
                  type: string
                  description: JID of this session's device, as stored after pairing.
                  example: 5511999999999:12@s.whatsapp.net
                linked_devices:
                  type: array
                  nullable: true
                  description: Other devices of the account; `null` when none were found.
                  items:
                    type: object
                    description: One registered device.
                    properties:
                      device_jid:
                        type: string
                        description: Device JID.
                        example: 5511999999999:7@s.whatsapp.net
                      device_id:
                        type: integer
                        description: Device slot number; 0 is the primary phone.
                        example: 7
                      platform:
                        type: string
                        description: Label derived from `device_id`, not the real client platform.
                        example: DESKTOP
                      is_current:
                        type: boolean
                        description: "`true` when the slot equals this session's device. Normally `false`, because the own device is not listed."
                        example: false
                count:
                  type: integer
                  description: Number of devices returned.
                  example: 2
    DevicePlatformRequest:
      type: object
      description: Device ID to translate.
      properties:
        device_id:
          type: integer
          description: Device slot number. Missing means 0.
          minimum: 0
          example: 7
    DevicePlatformResponse:
      description: Success envelope of `POST /device/platform`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Lookup result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                device_id:
                  type: integer
                  description: Echo of the requested device ID.
                  example: 7
                platform:
                  type: string
                  description: Platform label for the ID.
                  enum:
                    - UNKNOWN
                    - CHROME
                    - FIREFOX
                    - IE
                    - OPERA
                    - SAFARI
                    - EDGE
                    - DESKTOP
                    - IPAD
                    - ANDROID_TABLET
                    - OHANA
                    - ALOHA
                    - CATALINA
                    - TCL_TV
                  example: DESKTOP
    SetPrivacySettingRequest:
      type: object
      description: Privacy setting to change.
      required:
        - setting
        - value
      properties:
        setting:
          type: string
          description: Setting name (case-insensitive).
          enum:
            - group_add
            - last_seen
            - status
            - profile
            - read_receipts
            - online
            - call_add
          example: last_seen
        value:
          type: string
          description: New value (case-insensitive). WhatsApp may reject values a setting does not support.
          enum:
            - all
            - contacts
            - contact_blacklist
            - none
          example: contacts
        users:
          type: array
          description: Validated as JIDs but not applied; exception lists are not supported by this endpoint.
          items:
            type: string
            description: Phone number or JID.
            example: "5511977777777"
          example:
            - "5511977777777"
    PrivacySettingsResponse:
      description: Success envelope of `GET /privacy/settings`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Cached privacy settings.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                privacy_settings:
                  type: object
                  description: Setting values; empty strings when WhatsApp could not be queried.
                  properties:
                    group_add:
                      type: string
                      description: Who can add you to groups.
                      enum: [ all, contacts, contact_blacklist, none, "" ]
                      example: contacts
                    last_seen:
                      type: string
                      description: Who can see your last seen.
                      enum: [ all, contacts, contact_blacklist, none, "" ]
                      example: contacts
                    status:
                      type: string
                      description: Who can see your status updates.
                      enum: [ all, contacts, contact_blacklist, none, "" ]
                      example: contacts
                    profile:
                      type: string
                      description: Who can see your profile photo.
                      enum: [ all, contacts, contact_blacklist, none, "" ]
                      example: all
                    read_receipts:
                      type: string
                      description: Whether read receipts are sent.
                      enum: [ all, none, "" ]
                      example: all
                    online:
                      type: string
                      description: Who can see when you are online.
                      enum: [ all, match_last_seen, "" ]
                      example: match_last_seen
                    call_add:
                      type: string
                      description: Who can call you.
                      enum: [ all, known, "" ]
                      example: all
    DisappearingTimerRequest:
      type: object
      description: Default timer for new chats.
      required:
        - timer
      properties:
        timer:
          type: string
          description: Timer value (case-insensitive). `off` or `0` disables it.
          enum:
            - 24h
            - 7d
            - 90d
            - "off"
            - "0"
          example: 7d
    UpdateBlocklistRequest:
      type: object
      description: Contacts to block or unblock.
      required:
        - action
        - users
      properties:
        action:
          type: string
          description: Operation to apply (case-insensitive).
          enum:
            - block
            - unblock
          example: block
        users:
          type: array
          description: Phone numbers with country code or JIDs. At least one item.
          minItems: 1
          items:
            type: string
            description: Phone number or JID.
            example: "5511977777777"
          example:
            - "5511977777777"
            - 5511966666666@s.whatsapp.net
    BlocklistResponse:
      description: Success envelope of `GET /privacy/blocklist`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Blocklist.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                blocked_contacts:
                  type: array
                  nullable: true
                  description: Blocked JIDs; `null` when nobody is blocked.
                  items:
                    type: string
                  example:
                    - 5511977777777@s.whatsapp.net
                count:
                  type: integer
                  description: Number of blocked JIDs.
                  example: 1
    StatusPrivacyResponse:
      description: Success envelope of `GET /privacy/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Status privacy.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                status_privacy:
                  type: string
                  description: Who can see your status updates; empty when WhatsApp could not be queried.
                  enum: [ all, contacts, contact_blacklist, none, "" ]
                  example: contacts
                description:
                  type: string
                  description: Fixed explanatory text.
                  example: Status privacy setting (all, contacts, contact_blacklist, or none)
    CallRejectV1Request:
      type: object
      description: Body of `POST /call/reject`.
      required:
        - call_id
        - call_from
      properties:
        call_id:
          type: string
          description: Call ID from the call offer webhook.
          example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
        call_from:
          type: string
          description: Caller JID from the call offer webhook (`@s.whatsapp.net` or `@lid`).
          example: "5511999999999@s.whatsapp.net"
        reject_type:
          type: string
          description: Rejection label, echoed in the response. Defaults to `declined`.
          enum:
            - busy
            - declined
            - unavailable
          default: declined
          example: declined
        message:
          type: string
          description: Optional text sent to the caller after the rejection.
          example: "We can't take calls right now. Please send us a message and we'll reply shortly."
    CallRejectV1Response:
      description: Success envelope of `POST /call/reject`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Rejection result.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the rejection stanza was sent.
                  example: true
                call_id:
                  type: string
                  description: Echo of the requested call ID.
                  example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                call_from:
                  type: string
                  description: Caller JID normalized by the gateway.
                  example: "5511999999999@s.whatsapp.net"
                reject_type:
                  type: string
                  description: Rejection label used (the default `declined` when omitted).
                  enum:
                    - busy
                    - declined
                    - unavailable
                  example: declined
                action:
                  type: string
                  description: Always `rejected`.
                  enum:
                    - rejected
                  example: rejected
                message_sent:
                  type: boolean
                  description: "`true` only when a follow-up `message` was requested and WhatsApp accepted it."
                  example: false
      example:
        code: 200
        data:
          success: true
          call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
          call_from: "5511999999999@s.whatsapp.net"
          reject_type: declined
          action: rejected
          message_sent: false
        success: true
    CallAcceptV1Request:
      type: object
      description: Body of `POST /call/accept` and `POST /call/preaccept`.
      required:
        - call_id
        - call_from
      properties:
        call_id:
          type: string
          description: Call ID from the call offer webhook.
          example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
        call_from:
          type: string
          description: Caller JID from the call offer webhook (`@s.whatsapp.net` or `@lid`).
          example: "5511999999999@s.whatsapp.net"
    CallTerminateRequest:
      type: object
      description: Body of `POST /call/terminate`.
      required:
        - call_id
        - call_from
      properties:
        call_id:
          type: string
          description: Call ID from the call webhook.
          example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
        call_from:
          type: string
          description: JID of the other party of the call (`@s.whatsapp.net` or `@lid`).
          example: "5511999999999@s.whatsapp.net"
        reason:
          type: string
          description: Terminate reason forwarded to WhatsApp. Defaults to `hangup`.
          default: hangup
          example: hangup
    CallInitiateRequest:
      type: object
      required:
        - to
      properties:
        to:
          type: string
          example: 5511988888888@s.whatsapp.net
          description: Legacy field, not read by the current handler. Send the destination in `phone` instead; a body with only `to` returns `400 phone is required`.
          deprecated: true
        video:
          type: boolean
          default: false
          description: Place a video call (H.264) instead of audio-only. Defaults to false.
          example: false
      example:
        to: 5511988888888@s.whatsapp.net
        video: false
      description: Legacy body of `POST /call/initiate`. The route is now an alias of `/call/dial` and reads `phone`/`video` (see `CallDialRequest`); `to` is ignored.
    CallEngineConfig:
      type: object
      description: Body of `PUT /call/config`. Full replace; only `callProviderToken` is kept when omitted.
      properties:
        callsEnabled:
          type: boolean
          description: Runs the native calls engine for this instance after the next reconnect. Omitted means `false`.
          example: true
        callInboundMode:
          type: string
          description: How inbound calls are handled. Empty or omitted means `webhook`. `reject` keeps the engine off entirely. `bot`, `ivr` and `ai` are accepted, but the voice-agent is unavailable in this build and such calls are rejected.
          enum:
            - manual
            - bot
            - ivr
            - ai
            - webhook
            - reject
          default: webhook
          example: webhook
        callRecord:
          type: boolean
          description: Automatically records every connected call and uploads it to S3. Omitted means `false`.
          example: true
        callSttUrl:
          type: string
          description: Speech-to-text endpoint for the voice-agent. Omitted clears it.
          example: "https://stt.example.com/v1/transcribe"
        callLlmUrl:
          type: string
          description: Language-model endpoint for the voice-agent. Omitted clears it.
          example: "https://llm.example.com/v1/chat"
        callTtsUrl:
          type: string
          description: Text-to-speech endpoint for the voice-agent. Omitted clears it.
          example: "https://tts.example.com/v1/speak"
        callProviderToken:
          type: string
          writeOnly: true
          description: Bearer token for the provider endpoints. Write-only (never returned); kept when omitted.
          example: "sk-example-provider-token"
        callSystemPrompt:
          type: string
          description: System prompt for the voice-agent conversation. Omitted clears it.
          example: "You are the virtual receptionist of Example Store. Be brief and friendly."
        callGreeting:
          type: string
          description: Greeting spoken when the voice-agent answers. Omitted clears it.
          example: "Hello! Thanks for calling Example Store. How can I help you?"
    CallDialRequest:
      type: object
      description: Body of `POST /call/dial` and its alias `POST /call/initiate`.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Callee. Digits with country code and no `+` (resolved to the LID first), a phone JID (`@s.whatsapp.net`) or a LID (`@lid`). Group JIDs are not accepted.
          example: "5511988888888"
        video:
          type: boolean
          default: false
          description: "`true` places a video call (H.264); otherwise voice only."
          example: false
    CallIdRequest:
      type: object
      description: Body carrying only the engine call ID (`/call/answer`, `/call/hangup`, `/call/record/start`, `/call/record/stop`).
      required:
        - callId
      properties:
        callId:
          type: string
          description: Engine call ID from `/call/dial`, `GET /call/status` or the call webhooks.
          example: "call_17B3F2A1C9D04E8F"
    CallPlayRequest:
      type: object
      description: Body of `POST /call/play`. Send `callId` plus one audio source.
      required:
        - callId
      properties:
        callId:
          type: string
          description: Engine call ID of the live call.
          example: "call_17B3F2A1C9D04E8F"
        audioUrl:
          type: string
          format: uri
          description: Public URL of a WAV, Ogg/Opus or MP3 file (30 s download timeout, 64 MiB cap). Takes precedence over `audioBase64`.
          example: "https://cdn.example.com/audio/welcome-message.mp3"
        audioBase64:
          type: string
          format: byte
          description: Standard base64 of a WAV, Ogg/Opus or MP3 file, without a `data:` prefix. Used when `audioUrl` is empty.
          example: "UklGRiQAAABXQVZFZm10IBAAAAABAAEAgD4AAAB9AAACABAAZGF0YQAAAAA="
    CallInfo:
      type: object
      description: One live call tracked by the native calls engine.
      properties:
        callId:
          type: string
          description: Engine call ID.
          example: "call_17B3F2A1C9D04E8F"
        peer:
          type: string
          description: JID of the remote peer as tracked by the engine.
          example: "123456789012345@lid"
        state:
          type: string
          description: Current call phase.
          enum:
            - idle
            - calling
            - ringing
            - connecting
            - active
            - ended
            - unknown
          example: active
        direction:
          type: string
          description: Who started the call.
          enum:
            - inbound
            - outbound
          example: inbound
        video:
          type: boolean
          description: Whether the call carries video.
          example: false
        recording:
          type: boolean
          description: Whether a recording is currently running.
          example: true
        startedAt:
          type: string
          format: date-time
          description: When the engine started tracking the call.
          example: "2026-09-15T14:21:08.412Z"
    CallStatusResponse:
      description: Success envelope of `GET /call/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Live calls snapshot.
              properties:
                calls:
                  type: array
                  description: Live calls; empty when no call is in progress.
                  items:
                    $ref: "#/components/schemas/CallInfo"
      example:
        code: 200
        data:
          calls:
            - callId: "call_17B3F2A1C9D04E8F"
              peer: "123456789012345@lid"
              state: active
              direction: inbound
              video: false
              recording: true
              startedAt: "2026-09-15T14:21:08.412Z"
        success: true
    CallGroupDialRequest:
      type: object
      description: Body of `POST /call/group/dial`.
      required:
        - targets
      properties:
        targets:
          type: array
          minItems: 2
          maxItems: 31
          description: People to ring (at least two). WhatsApp accepts at most 31 remote targets.
          items:
            type: string
            description: Phone number with country code, phone JID or LID.
            example: "5511988888888"
          example:
            - "5511988888888"
            - "123456789012345@lid"
        video:
          type: boolean
          default: false
          description: "`true` offers H.264 video to every participant."
          example: false
        groupJid:
          type: string
          description: Optional group the call is bound to while only `targets` are rung.
          example: "120363000000000011@g.us"
    CallGroupDialByIDRequest:
      type: object
      description: Body of `POST /call/group/dial-by-id`.
      required:
        - groupJid
      properties:
        groupJid:
          type: string
          description: WhatsApp group whose members are rung.
          example: "120363000000000011@g.us"
        video:
          type: boolean
          default: false
          description: "`true` offers H.264 video."
          example: false
    CallParticipantsAddRequest:
      type: object
      description: Body of `POST /call/participants/add`.
      required:
        - callId
        - targets
      properties:
        callId:
          type: string
          description: Engine call ID of the live call.
          example: "call_5C1E9A7B3D2F4E60"
        targets:
          type: array
          minItems: 1
          description: People to invite; each is invited independently.
          items:
            type: string
            description: Phone number with country code, phone JID or LID.
            example: "5511966666666"
          example:
            - "5511966666666"
    CallParticipantRingRequest:
      type: object
      description: Body of `POST /call/participants/ring`. Values must be strings.
      required:
        - callId
        - target
      properties:
        callId:
          type: string
          description: Engine call ID of the live group call.
          example: "call_5C1E9A7B3D2F4E60"
        target:
          type: string
          description: Roster participant to ring again (phone number, phone JID or LID).
          example: "5511977777777"
    CallLinkCreateRequest:
      type: object
      description: Optional body of `POST /call/link/create`.
      properties:
        video:
          type: boolean
          default: false
          description: "`true` creates a video link; otherwise a voice link."
          example: true
    CallLinkInfo:
      type: object
      description: A created WhatsApp call link.
      properties:
        token:
          type: string
          description: Call-link token.
          example: "Xk3pQ9aB7cD2eF4g"
        url:
          type: string
          format: uri
          description: Shareable link, `https://call.whatsapp.com/audio/<token>` or `/video/<token>`.
          example: "https://call.whatsapp.com/video/Xk3pQ9aB7cD2eF4g"
        video:
          type: boolean
          description: Whether it is a video link.
          example: true
    CallLinkPreviewInfo:
      type: object
      description: Call-link metadata read without joining.
      properties:
        token:
          type: string
          description: Normalized call-link token.
          example: "Xk3pQ9aB7cD2eF4g"
        video:
          type: boolean
          description: Whether the link is a video link.
          example: true
        approvalRequired:
          type: boolean
          description: Whether joiners wait in the waiting room for an admin.
          example: true
        isAdmin:
          type: boolean
          description: Whether this account is an admin of the link.
          example: false
        creator:
          type: string
          description: JID of the link creator. Omitted when not disclosed.
          example: "123456789012345@lid"
        creatorPhone:
          type: string
          description: Phone JID of the creator. Omitted when not disclosed.
          example: "5511988888888@s.whatsapp.net"
    CallLinkJoinRequest:
      type: object
      description: Body of `POST /call/link/join`.
      required:
        - token
      properties:
        token:
          type: string
          description: Call-link token or full `https://call.whatsapp.com/audio|video/<token>` URL.
          example: "Xk3pQ9aB7cD2eF4g"
        video:
          type: boolean
          default: false
          description: "`true` joins with video."
          example: false
    CallWaitingRoomUserRequest:
      type: object
      description: Body of `POST /call/waiting-room/admit` and `/call/waiting-room/deny`. Values must be strings.
      required:
        - callId
        - user
      properties:
        callId:
          type: string
          description: Engine call ID of the live call-link call.
          example: "call_8D4B2E6A1C9F3E70"
        user:
          type: string
          description: JID of the waiting user, as listed by `GET /call/{call_id}/waiting-room`.
          example: "123456789012345@lid"
    CallApprovalRequest:
      type: object
      description: Body of `POST /call/waiting-room/approval`.
      required:
        - callId
      properties:
        callId:
          type: string
          description: Engine call ID of the live call-link call.
          example: "call_8D4B2E6A1C9F3E70"
        enabled:
          type: boolean
          default: true
          description: "`true` requires approval (waiting room on), `false` lets joiners in directly. Must be a JSON boolean."
          example: true
    CallReactionRequest:
      type: object
      description: Body of `POST /call/reaction`. Values must be strings.
      required:
        - callId
        - emoji
      properties:
        callId:
          type: string
          description: Engine call ID of the live call.
          example: "call_5C1E9A7B3D2F4E60"
        emoji:
          type: string
          minLength: 1
          description: Emoji to show to the participants.
          example: "👍"
    CallHandRequest:
      type: object
      description: Body of `POST /call/hand`.
      required:
        - callId
      properties:
        callId:
          type: string
          description: Engine call ID of the live call.
          example: "call_5C1E9A7B3D2F4E60"
        raised:
          type: boolean
          default: true
          description: "`true` raises the hand, `false` lowers it. Must be a JSON boolean."
          example: true
    CallVideoToggleRequest:
      type: object
      description: Body of `POST /call/video`.
      required:
        - callId
      properties:
        callId:
          type: string
          description: Engine call ID of the live call.
          example: "call_17B3F2A1C9D04E8F"
        enabled:
          type: boolean
          default: true
          description: "`true` requests the upgrade to video, `false` stops outbound video. Must be a JSON boolean."
          example: true
    CallScreenShareRequest:
      type: object
      description: Body of `POST /call/screenshare`.
      required:
        - callId
      properties:
        callId:
          type: string
          description: Engine call ID of the live call.
          example: "call_5C1E9A7B3D2F4E60"
        enabled:
          type: boolean
          default: true
          description: "`true` announces a screen share, `false` ends it. Must be a JSON boolean."
          example: true
    CallGroupState:
      type: object
      description: Roster of a live group call.
      properties:
        callId:
          type: string
          description: Engine call ID.
          example: "call_5C1E9A7B3D2F4E60"
        transactionId:
          type: integer
          description: WhatsApp roster transaction counter; higher values are newer.
          example: 4
        participants:
          type: array
          description: Participants in the roster, including this account.
          items:
            type: object
            description: One roster entry.
            properties:
              jid:
                type: string
                description: Participant JID (usually a LID).
                example: "123456789012345@lid"
              phone:
                type: string
                description: Phone JID when WhatsApp provided it. Omitted otherwise.
                example: "5511988888888@s.whatsapp.net"
              state:
                type: string
                description: Raw WhatsApp roster state, for example `connected`, `outgoing`, `receipt` or `invited`.
                example: connected
    CallWaitingRoomState:
      type: object
      description: Waiting-room state of a live call-link call.
      properties:
        callId:
          type: string
          description: Engine call ID.
          example: "call_8D4B2E6A1C9F3E70"
        enabled:
          type: boolean
          description: Whether approval is required to join.
          example: true
        isAdmin:
          type: boolean
          description: Whether this account can admit or deny users.
          example: true
        inWaitingRoom:
          type: boolean
          description: Whether this account itself is waiting to be admitted.
          example: false
        transactionId:
          type: integer
          description: WhatsApp waiting-room transaction counter; higher values are newer.
          example: 2
        users:
          type: array
          description: Users waiting for admission.
          items:
            type: object
            description: One waiting user.
            properties:
              jid:
                type: string
                description: JID of the waiting user; pass it as `user` to admit or deny.
                example: "123456789012346@lid"
              phone:
                type: string
                description: Phone JID when WhatsApp provided it. Omitted otherwise.
                example: "5511977777777@s.whatsapp.net"
    ChatReplyRequest:
      type: object
      description: Text reply that quotes a previous message.
      required:
        - to
        - quoted_msg_id
        - content_type
      properties:
        to:
          type: string
          description: Chat of the reply and of the quoted message. Phone number, user JID or group JID.
          example: 5511999999999@s.whatsapp.net
        quoted_msg_id:
          type: string
          description: ID of the message being quoted.
          example: 3EB0F2A1C9D04E8F1234
        quoted_from_me:
          type: boolean
          default: false
          description: "`true` when the quoted message was sent by this account."
          example: false
        quoted_sender:
          type: string
          description: Author JID of the quoted message (needed in groups). Defaults to `to` when omitted or unparsable.
          example: 5511888888888@s.whatsapp.net
        content_type:
          type: string
          description: Only `text` is supported; `image`, `video`, `document` and `audio` return 400.
          enum:
            - text
            - image
            - video
            - document
            - audio
          example: text
        content:
          type: string
          description: Reply text. Required when `content_type` is `text`.
          example: Perfect, confirmed for tomorrow at 10 AM.
      example:
        to: 5511999999999@s.whatsapp.net
        quoted_msg_id: 3EB0F2A1C9D04E8F1234
        quoted_from_me: false
        content_type: text
        content: Perfect, confirmed for tomorrow at 10 AM.
    OrderDetailsResponse:
      type: object
      description: Order resolved by ID and token.
      properties:
        success:
          type: boolean
          description: Always true on success.
          example: true
        order:
          type: object
          description: Order details returned by WhatsApp.
          properties:
            id:
              type: string
              description: Order ID.
              example: "1976123456789012"
            created_at:
              type: string
              description: Order creation time.
              format: date-time
              example: "2026-09-15T11:30:00Z"
            catalog_id:
              type: string
              description: Seller catalog ID, when present.
              example: "7123456789012300"
            price:
              type: object
              description: Order totals.
              properties:
                subtotal:
                  type: integer
                  description: Subtotal in thousandths of the currency.
                  example: 6000000
                total:
                  type: integer
                  description: Total in thousandths of the currency.
                  example: 6000000
                currency:
                  type: string
                  description: ISO-4217 currency.
                  example: BRL
                price_status:
                  type: string
                  description: Price status reported by WhatsApp, when present.
                  example: PROVIDED
            products:
              type: array
              description: Ordered products.
              items:
                type: object
                properties:
                  id:
                    type: string
                    description: Catalog product ID.
                    example: "7123456789012345"
                  image_id:
                    type: string
                    description: Product image ID, when present.
                    example: "7123456789012346"
                  image_url:
                    type: string
                    description: Product image URL, when present.
                    example: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg
                  price:
                    type: integer
                    description: Unit price in thousandths of the currency.
                    example: 3000000
                  currency:
                    type: string
                    description: ISO-4217 currency.
                    example: BRL
                  name:
                    type: string
                    description: Product name.
                    example: Wooden chair
                  quantity:
                    type: integer
                    description: Quantity.
                    example: 2
                  variant_info:
                    type: object
                    description: Variant properties, when present.
                    properties:
                      properties:
                        type: string
                        description: Serialized variant properties.
                        example: "Color: Natural"
    BusinessCatalogSendRequest:
      type: object
      description: Catalog link message.
      required:
        - to
        - business_jid
      properties:
        to:
          type: string
          description: "Recipient chat: phone number or JID (user, LID or group)."
          example: 5511999999999@s.whatsapp.net
        business_jid:
          type: string
          description: "Catalog owner: phone number, non-device phone JID, or `@lid` with a stored phone mapping."
          example: 5511988888888@s.whatsapp.net
        caption:
          type: string
          description: Optional text placed before the link, separated by a blank line.
          example: Check out our full catalog
      example:
        to: 5511999999999@s.whatsapp.net
        business_jid: 5511988888888@s.whatsapp.net
        caption: Check out our full catalog
    BusinessProductSendRequest:
      type: object
      description: Single product card message.
      required:
        - to
        - business_jid
        - product_id
      properties:
        to:
          type: string
          description: "Recipient chat: phone number or JID."
          example: 5511999999999@s.whatsapp.net
        business_jid:
          type: string
          description: Catalog owner (phone number or JID). A phone JID is replaced by the owner's LID when the mapping is known.
          example: 5511988888888@s.whatsapp.net
        product_id:
          type: string
          description: Catalog product ID.
          example: "7123456789012345"
        caption:
          type: string
          description: "ID-only mode: product description. With `snapshot`/`use_catalog`: card body (at most 1024 bytes)."
          example: Our best seller
        quoted_msg_id:
          type: string
          description: Optional ID of a real outgoing message stored for this session and chat to quote. Unknown IDs are ignored and reported in `quoted_message_reason`.
          example: 3EB0C767D26A1D6B8A9F
        use_catalog:
          type: boolean
          description: True loads title, description, price, retailer ID, URL and first image from the owner's catalog.
          default: false
          example: false
        snapshot:
          type: object
          description: "Explicit product card fields. With `use_catalog: true`, only the fields sent here override the catalog values."
          properties:
            title:
              type: string
              description: Card title, required in explicit mode, at most 256 bytes.
              example: Wooden chair
            description:
              type: string
              description: Card description, at most 4096 bytes.
              example: Solid wood chair with natural finish.
            currency:
              type: string
              description: ISO-4217 uppercase currency; required when a price is present.
              example: BRL
            price_amount1000:
              type: integer
              description: Price in integer thousandths of the currency (`3000000` = 3,000.00).
              example: 3000000
            retailer_id:
              type: string
              description: Retailer ID, at most 256 bytes.
              example: CHAIR-001
            url:
              type: string
              description: Absolute HTTPS product URL, at most 2048 bytes.
              example: https://example.com/products/chair
            image:
              type: string
              description: JPEG/PNG as base64 or `data:image/jpeg|png;base64,` URI (up to 16 MiB and 40 megapixels). URLs are not fetched. Replaces the catalog image with `use_catalog`.
              example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
      example:
        to: 5511999999999@s.whatsapp.net
        business_jid: 5511988888888@s.whatsapp.net
        product_id: "7123456789012345"
        caption: Our best seller
    BusinessProductListSendRequest:
      type: object
      description: Product list message with sections.
      required:
        - to
        - business_jid
        - sections
      properties:
        to:
          type: string
          description: "Recipient chat: phone number or JID."
          example: 5511999999999@s.whatsapp.net
        business_jid:
          type: string
          description: Catalog owner JID (phone or LID).
          example: 5511988888888@s.whatsapp.net
        header:
          type: string
          description: List title, at most 60 bytes. Defaults to the Portuguese label `Catálogo` when empty.
          example: Weekly offers
        body:
          type: string
          description: Optional list description, at most 1024 bytes.
          example: Select the items you want
        footer:
          type: string
          description: Optional footer, at most 60 bytes.
          example: Valid until Sunday
        use_catalog:
          type: boolean
          description: True adds a header thumbnail from a catalog product image.
          default: false
          example: false
        header_product_id:
          type: string
          description: Product whose image becomes the header thumbnail with `use_catalog`; must belong to `sections` (defaults to the first product).
          example: "7123456789012346"
        sections:
          type: array
          description: 1-10 sections with at most 30 products in total; product IDs must be unique across sections. A title is required when there is more than one section.
          minItems: 1
          maxItems: 10
          items:
            type: object
            properties:
              title:
                type: string
                description: Section title, at most 24 bytes.
                example: Chairs
              products:
                type: array
                description: Products of the section (at least one).
                items:
                  type: object
                  properties:
                    product_id:
                      type: string
                      description: Catalog product ID.
                      example: "7123456789012345"
          example:
            - title: Chairs
              products:
                - product_id: "7123456789012345"
                - product_id: "7123456789012346"
            - title: Tables
              products:
                - product_id: "7123456789012347"
      example:
        to: 5511999999999@s.whatsapp.net
        business_jid: 5511988888888@s.whatsapp.net
        header: Weekly offers
        body: Select the items you want
        footer: Valid until Sunday
        sections:
          - title: Chairs
            products:
              - product_id: "7123456789012345"
              - product_id: "7123456789012346"
    BusinessShopSendRequest:
      type: object
      description: Shop link message.
      required:
        - to
        - business_jid
      properties:
        to:
          type: string
          description: "Recipient chat: phone number or JID."
          example: 5511999999999@s.whatsapp.net
        business_jid:
          type: string
          description: "Shop owner: phone number, non-device phone JID, or `@lid` with a stored phone mapping."
          example: 5511988888888@s.whatsapp.net
        caption:
          type: string
          description: Text placed before the link, separated by a space. Defaults to the Portuguese label `Acesse nossa loja:` when empty.
          example: "Visit our official shop:"
      example:
        to: 5511999999999@s.whatsapp.net
        business_jid: 5511988888888@s.whatsapp.net
        caption: "Visit our official shop:"
    PasskeyResponsePayload:
      type: object
      required:
        - id
        - rawId
        - type
        - response
      properties:
        id:
          type: string
          description: Credential id.
          example: credential-id
        rawId:
          type: string
          description: Base64url-encoded raw credential id (no padding).
          example: base64url-encoded-raw-id
        type:
          type: string
          description: WebAuthn credential type.
          example: public-key
        response:
          type: object
          required:
            - clientDataJSON
            - authenticatorData
            - signature
          properties:
            clientDataJSON:
              type: string
              description: Base64url-encoded clientDataJSON (no padding).
              example: base64url-encoded-client-data
            authenticatorData:
              type: string
              description: Base64url-encoded authenticatorData (no padding).
              example: base64url-encoded-authenticator-data
            signature:
              type: string
              description: Base64url-encoded signature (no padding).
              example: base64url-encoded-signature
            userHandle:
              type: string
              nullable: true
              description: Optional base64url-encoded user handle.
              example: base64url-encoded-user-handle
      example:
        id: credential-id
        rawId: base64url-encoded-raw-id
        type: public-key
        response:
          clientDataJSON: base64url-encoded-client-data
          authenticatorData: base64url-encoded-authenticator-data
          signature: base64url-encoded-signature
    WebKeyPair:
      type: object
      required:
        - priv
      properties:
        priv:
          type: string
          description: Base64 (std or url) 32-byte Curve25519 private key.
        pub:
          type: string
          description: Optional base64 public key — derived from priv when omitted.
    WebSignedPreKey:
      type: object
      required:
        - keyId
        - priv
        - signature
      properties:
        keyId:
          type: integer
          format: int64
        priv:
          type: string
          description: Base64 32-byte private key.
        pub:
          type: string
          description: Optional base64 public key.
        signature:
          type: string
          description: Base64 64-byte identity-key signature.
    WebAccount:
      type: object
      required:
        - details
        - accountSignature
        - accountSignatureKey
        - deviceSignature
      properties:
        details:
          type: string
          description: Base64 ADVSignedDeviceIdentity details.
        accountSignature:
          type: string
        accountSignatureKey:
          type: string
        deviceSignature:
          type: string
    WebDeviceSnapshot:
      type: object
      description: |
        Portable intermediate representation of a web.whatsapp.com device identity,
        consumed by POST /session/import. All binary fields are base64 (std or url).
      required:
        - meJid
        - registrationId
        - noiseKey
        - identityKey
        - signedPreKey
        - account
      properties:
        meJid:
          type: string
          description: The device JID, e.g. 5521999999999:0@s.whatsapp.net.
        lid:
          type: string
          description: Optional LID.
        registrationId:
          type: integer
          format: int64
        noiseKey:
          $ref: "#/components/schemas/WebKeyPair"
        identityKey:
          $ref: "#/components/schemas/WebKeyPair"
        signedPreKey:
          $ref: "#/components/schemas/WebSignedPreKey"
        account:
          $ref: "#/components/schemas/WebAccount"
        advSecretKey:
          type: string
          description: Optional base64 32-byte app-state secret.
        platform:
          type: string
        pushName:
          type: string
        businessName:
          type: string
    main.AddUserNetworkRouteFailureResponse:
      type: object
      properties:
        code:
          type: integer
          example: 503
        data:
          type: object
          properties:
            network_route:
              $ref: "#/components/schemas/main.NetworkRouteView"
            userId:
              type: string
              example: 5663e0c52063ec35d0ba45f7c2011c06
        details:
          type: string
          example: user persisted in blocked state; submit a higher routeVersion with a new one-use auth key or delete this user
        error:
          type: string
          example: network_route_prepare_failed
        success:
          type: boolean
          example: false
    main.BusinessManagementAppealRequest:
      type: object
      properties:
        reason:
          description: Appeal reason, required, non-blank, at most 1024 bytes.
          type: string
          example: The product is a handmade wooden chair and complies with the commerce policies.
          minLength: 1
          maxLength: 1024
      required:
        - reason
      x-validation-constraints:
        required:
          - reason
        properties:
          reason:
            example: The product is a handmade wooden chair and complies with the commerce policies.
            minLength: 1
            maxLength: 1024
    main.BusinessManagementCollectionRequest:
      type: object
      properties:
        name:
          description: Collection name, required, 1-256 bytes (trimmed).
          type: string
          example: Chairs
          minLength: 1
          maxLength: 256
        product_ids:
          description: Own product IDs to include. The gateway accepts 0-100 unique IDs, but WhatsApp requires 1-100 (an empty list returns `502`).
          type: array
          items:
            type: string
          example:
            - "7123456789012345"
          maxItems: 100
          uniqueItems: true
      required:
        - name
      x-validation-constraints:
        required:
          - name
        properties:
          name:
            example: Chairs
            minLength: 1
            maxLength: 256
          product_ids:
            description: Own product IDs to include. The gateway accepts 0-100 unique IDs, but WhatsApp requires 1-100 (an empty list returns `502`).
            example:
              - "7123456789012345"
            maxItems: 100
            uniqueItems: true
    main.BusinessManagementCollectionsRequest:
      type: object
      properties:
        collection_ids:
          description: Collection IDs to delete, 1-100 unique, each 1-256 bytes.
          type: array
          items:
            type: string
          example:
            - "7123456789012350"
          minItems: 1
          maxItems: 100
          uniqueItems: true
      required:
        - collection_ids
      x-validation-constraints:
        required:
          - collection_ids
        properties:
          collection_ids:
            example:
              - "7123456789012350"
            minItems: 1
            maxItems: 100
            uniqueItems: true
    main.BusinessManagementEligibilityRequest:
      type: object
      properties:
        features:
          description: Features to check, 1-6 unique values.
          type: array
          items:
            $ref: "#/components/schemas/types.BusinessFeature"
          example:
            - meta_verified
            - bb_pro
          minItems: 1
          maxItems: 6
      required:
        - features
      x-validation-constraints:
        required:
          - features
        properties:
          features:
            example:
              - meta_verified
              - bb_pro
            minItems: 1
            maxItems: 6
    main.BusinessManagementEnabledRequest:
      type: object
      properties:
        enabled:
          description: Required. true enables the shopping cart; false disables it.
          type: boolean
          example: true
      required:
        - enabled
      x-validation-constraints:
        required:
          - enabled
        properties:
          enabled:
            example: true
    main.BusinessManagementHiddenRequest:
      type: object
      properties:
        hidden:
          description: Required. true hides the product from public catalog reads; false shows it.
          type: boolean
          example: true
      required:
        - hidden
      x-validation-constraints:
        required:
          - hidden
        properties:
          hidden:
            example: true
    main.BusinessManagementIDsRequest:
      type: object
      properties:
        product_ids:
          description: Product IDs, 1-100 unique, each 1-256 bytes.
          type: array
          items:
            type: string
          example:
            - "7123456789012345"
            - "7123456789012346"
          minItems: 1
          maxItems: 100
          uniqueItems: true
      required:
        - product_ids
      x-validation-constraints:
        required:
          - product_ids
        properties:
          product_ids:
            example:
              - "7123456789012345"
              - "7123456789012346"
            minItems: 1
            maxItems: 100
            uniqueItems: true
    main.BusinessManagementImageRequest:
      type: object
      properties:
        image:
          description: JPEG or PNG bytes as standard base64 without data URI prefix (1 byte to 16 MiB; cover photos up to 5 MiB).
          type: string
          format: byte
          example: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
      required:
        - image
      x-validation-constraints:
        required:
          - image
        properties:
          image:
            example: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
    main.BusinessManagementMovesRequest:
      type: object
      properties:
        moves:
          description: Collection moves, 1-100, unique collection_id, zero-based indices below 100.
          type: array
          items:
            $ref: "#/components/schemas/types.BusinessCollectionMove"
          example:
            - collection_id: "7123456789012352"
              from_index: 1
              to_index: 0
          minItems: 1
          maxItems: 100
      required:
        - moves
      x-validation-constraints:
        required:
          - moves
        properties:
          moves:
            example:
              - collection_id: "7123456789012352"
                from_index: 1
                to_index: 0
            minItems: 1
            maxItems: 100
    main.BusinessManagementProductRequest:
      type: object
      properties:
        height:
          description: "Height in pixels of product media returned in the response: 0 (default 100) or 1-1024."
          type: integer
          example: 100
          minimum: 0
          maximum: 1024
        product:
          allOf:
            - $ref: "#/components/schemas/types.BusinessProductInput"
          description: Complete product fields. On PUT every omitted field is removed from the product.
          example:
            name: Wooden chair
            description: Solid wood chair with natural finish.
            url: https://example.com/products/chair
            retailer_id: CHAIR-001
            currency: BRL
            price: "3000000"
            sale_price: "2499900"
            is_hidden: false
            image_urls:
              - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
            compliance_info:
              country_code_origin: BR
        width:
          description: "Width in pixels of product media returned in the response: 0 (default 100) or 1-1024."
          type: integer
          example: 100
          minimum: 0
          maximum: 1024
      required:
        - product
      x-validation-constraints:
        required:
          - product
        properties:
          product:
            description: Complete product fields. On PUT every omitted field is removed from the product.
            example:
              name: Wooden chair
              description: Solid wood chair with natural finish.
              url: https://example.com/products/chair
              retailer_id: CHAIR-001
              currency: BRL
              price: "3000000"
              sale_price: "2499900"
              is_hidden: false
              image_urls:
                - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
              compliance_info:
                country_code_origin: BR
          width:
            example: 100
            minimum: 0
            maximum: 1024
          height:
            example: 100
            minimum: 0
            maximum: 1024
    main.HealthBuildInfo:
      type: object
      properties:
        commit:
          type: string
          example: a1b2c3d4
    main.HealthCheckMetrics:
      type: object
      properties:
        cpu_count:
          type: integer
          example: 8
        go_max_procs:
          type: integer
          example: 8
        go_version:
          type: string
          example: go1.21.5
        goroutines:
          type: integer
          example: 42
    main.HealthCheckResponse:
      type: object
      properties:
        build:
          $ref: "#/components/schemas/main.HealthBuildInfo"
        capabilities:
          $ref: "#/components/schemas/main.HealthCheckCapabilities"
        code:
          type: integer
          example: 200
        data:
          type: string
          example: OK
        environment:
          $ref: "#/components/schemas/main.HealthEnvironmentMetadata"
        metrics:
          $ref: "#/components/schemas/main.HealthCheckMetrics"
        public_url:
          type: string
        services:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/main.HealthServiceStatus"
        status:
          type: string
          example: healthy
        success:
          type: boolean
          example: true
        timestamp:
          type: string
          example: "2024-01-01T12:00:00Z"
        uptime:
          type: integer
          example: 86400
        version:
          type: string
          example: 3.0.0
    main.HealthEnvironmentMetadata:
      type: object
      properties:
        app_env:
          type: string
          example: production
        app_region:
          type: string
          example: us-east-1
        containerized:
          type: boolean
          example: false
        hostname:
          type: string
          example: zuckzapgo-01
    main.HealthServiceStatus:
      type: object
      properties:
        driver:
          type: string
          example: postgres
        error:
          type: string
          example: database not configured
        idle:
          type: integer
          example: 8
        in_use:
          type: integer
          example: 2
        latency_ms:
          type: number
          example: 12.5
        open_connections:
          type: integer
          example: 10
        status:
          type: string
          example: up
    main.NetworkRouteApplyResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        data:
          $ref: "#/components/schemas/main.NetworkRouteView"
        details:
          type: string
          example: network route ready
        reconnectScheduled:
          type: boolean
          example: false
        success:
          type: boolean
          example: true
    main.NetworkRouteErrorResponse:
      type: object
      properties:
        code:
          type: integer
          example: 503
        data:
          $ref: "#/components/schemas/main.NetworkRouteView"
        details:
          type: string
        error:
          type: string
          example: network_route_prepare_failed
        success:
          type: boolean
          example: false
    main.NetworkRouteGetResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        data:
          $ref: "#/components/schemas/main.NetworkRouteView"
        success:
          type: boolean
          example: true
    main.NetworkRouteRequest:
      type: object
      properties:
        authKey:
          description: AuthKey is a JIT, single-use Tailscale auth key. It is write-only and is never persisted, returned, or logged.
          type: string
          x-writeonly: "true"
          example: tskey-auth-REDACTED
        exitNodeId:
          description: ExitNodeID is the stable Tailscale node ID, not a mutable hostname or IP.
          type: string
          example: nExampleStableExitNode
        failClosed:
          description: |-
            FailClosed decides what happens while the route is not ready: `true` holds the traffic;
            `false` (also the value when the field is omitted) lets the session leave through the default egress. Not holding traffic is an explicit operator
            choice with a cost (the peer sees the session coming from another IP), so it is never inferred.
          type: boolean
          example: true
        idempotencyKey:
          description: IdempotencyKey identifies exactly one desired version and must be reused only for an identical retry.
          type: string
          example: route-2026-08-15-0001
        mode:
          description: Mode only accepts "mobile". Direct egress is deliberately unsupported.
          type: string
          enum:
            - mobile
          example: mobile
        routeVersion:
          description: RouteVersion is a positive, monotonically increasing fencing token.
          type: integer
          minimum: 1
          example: 1
        tcpOnly:
          description: TCPOnly must be true. Native Calls/UDP is disabled while this route is desired.
          type: boolean
          example: true
    main.NetworkRouteView:
      type: object
      properties:
        appliedVersion:
          type: integer
          example: 1
        desiredVersion:
          type: integer
          example: 1
        errorCode:
          type: string
          example: network_route_prepare_failed
        exitNodeId:
          type: string
          example: nExampleStableExitNode
        failClosed:
          type: boolean
          example: true
        mode:
          type: string
          enum:
            - mobile
          example: mobile
        ready:
          type: boolean
          example: true
        status:
          type: string
          enum:
            - preparing
            - ready
            - blocked
          example: ready
        tcpOnly:
          type: boolean
          example: true
    main.NewsletterTargetRequest:
      type: object
      properties:
        Code:
          description: Channel invite code or `https://whatsapp.com/channel/<code>` link (max 2048 bytes) for follow-invite/unfollow-invite; aliases code, invite_code.
          type: string
          example: 0029VaExampleInviteCode01
        NewsletterJID:
          description: Newsletter JID (`<digits>@newsletter`) for follow, unfollow, info, messages; aliases newsletter_jid, jid, NewsletterID.
          type: string
          example: 120363000000000022@newsletter
        before_id:
          description: On /newsletter/messages, return posts older than this server message ID (string or integer); aliases before, Before.
          type: string
          example: "120"
        limit:
          description: Posts to return on /newsletter/messages (1-1000, default 25); aliases count, Count.
          type: integer
          example: 25
        newsletter_jid:
          description: Snake-case alias of NewsletterJID; must be identical when both are sent.
          type: string
          example: 120363000000000022@newsletter
    main.PasskeyResponsePayload:
      type: object
      properties:
        id:
          description: Credential ID from the WebAuthn assertion, sent as-is (required).
          type: string
          example: Y3JlZGVudGlhbC1pZC0wMDAx
        rawId:
          description: Raw credential ID, base64url without padding (required).
          type: string
          example: Y3JlZGVudGlhbC1pZC0wMDAx
        response:
          description: Authenticator assertion data; all values base64url without padding.
          type: object
          properties:
            authenticatorData:
              description: Authenticator data bytes, base64url without padding (required).
              type: string
              example: ZmljdGl0aW91cy1hdXRoZW50aWNhdG9yLWRhdGE
            clientDataJSON:
              description: Client data JSON bytes, base64url without padding (required).
              type: string
              example: eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWIud2hhdHNhcHAuY29tIn0
            signature:
              description: Assertion signature bytes, base64url without padding (required).
              type: string
              example: ZmljdGl0aW91cy1zaWduYXR1cmUtYnl0ZXM
            userHandle:
              description: Optional user handle bytes, base64url without padding.
              type: string
              example: dXNlci1oYW5kbGUtMDAwMQ
        type:
          description: Credential type, normally public-key (required).
          type: string
          example: public-key
    main.UserContactLinkResolveRequest:
      type: object
      properties:
        code:
          description: Contact QR code or https://wa.me/qr/ URL, 1-2048 bytes, no whitespace (required).
          type: string
          maxLength: 2048
          minLength: 1
          example: https://wa.me/qr/ABCDEFGHIJKL12
    main.UserContactLinkResolveResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/types.ContactQRLinkTarget"
        success:
          type: boolean
    main.UserIdentityCodesResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/whatsmeow.IdentityVerificationCodes"
        success:
          type: boolean
    main.UserUsernameResolveRequest:
      type: object
      properties:
        key:
          description: Optional four-digit key required by some usernames.
          type: string
          maxLength: 4
          minLength: 4
          example: "4821"
        username:
          description: WhatsApp username, 3-35 chars, optional leading @, no whitespace (required).
          type: string
          example: examplestore
    main.UserUsernameResolveResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/types.UsernameResolution"
        success:
          type: boolean
    main.WebAccount:
      type: object
      properties:
        accountSignature:
          description: Account signature issued at pairing, base64 (required).
          type: string
          example: FRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVA==
        accountSignatureKey:
          description: Public key that produced the account signature, base64 (required).
          type: string
          example: HyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4=
        details:
          description: Serialized ADVDeviceIdentity details, base64 (required).
          type: string
          example: CwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6
        deviceSignature:
          description: Device signature, base64 (required).
          type: string
          example: KSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaA==
    main.WebDeviceSnapshot:
      type: object
      properties:
        account:
          description: ADVSignedDeviceIdentity issued by WhatsApp at pairing (required).
          allOf:
            - $ref: "#/components/schemas/main.WebAccount"
        advSecretKey:
          description: Optional ADV secret key, base64; a fresh random key is kept when omitted.
          type: string
          example: MzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVI=
        businessName:
          description: Optional business name stored on the device.
          type: string
          example: Example Store
        identityKey:
          description: Signal identity key pair of the device (required).
          allOf:
            - $ref: "#/components/schemas/main.WebKeyPair"
        lid:
          description: Optional device LID JID, e.g. 123456789012345:12@lid.
          type: string
          example: 123456789012345:12@lid
        meJid:
          description: Device JID of the imported session, e.g. 5511999999999:12@s.whatsapp.net (required).
          type: string
          example: 5511999999999:12@s.whatsapp.net
        noiseKey:
          description: Noise protocol key pair of the device (required).
          allOf:
            - $ref: "#/components/schemas/main.WebKeyPair"
        platform:
          description: Optional platform label stored on the device.
          type: string
          example: chrome
        pushName:
          description: Optional push name stored on the device.
          type: string
          example: Support Line
        registrationId:
          description: Signal registration ID of the device; must be non-zero (required).
          type: integer
          example: 12345
        signedPreKey:
          description: Signed pre-key with its identity-key signature (required).
          allOf:
            - $ref: "#/components/schemas/main.WebSignedPreKey"
    main.WebKeyPair:
      type: object
      properties:
        priv:
          description: Private key, 32 bytes, base64 (standard or URL alphabet, padded or not) (required).
          type: string
          example: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=
        pub:
          description: Optional public key, base64; ignored because it is derived from priv.
          type: string
          example: yVb3ZlK0exampleDerivedPublicKey0000000000=
    main.WebSignedPreKey:
      type: object
      properties:
        keyId:
          description: Signed pre-key ID.
          type: integer
          example: 1
        priv:
          description: Private key, 32 bytes, base64 (required).
          type: string
          example: QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2A=
        pub:
          description: Optional public key, base64; ignored because it is derived from priv.
          type: string
          example: Zm9vYmFyZXhhbXBsZURlcml2ZWRQdWJsaWNLZXkwMA==
        signature:
          description: Signature of the public key by the identity key, 64 bytes, base64 (required).
          type: string
          example: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foA==
    types.BusinessAddress:
      type: object
      properties:
        city:
          type: string
          description: City, at most 256 bytes.
          example: Springfield
          maxLength: 256
        country_code:
          type: string
          description: ISO-3166 alpha-2 country code (at most 3 bytes).
          example: US
          maxLength: 3
        postal_code:
          type: string
          description: Postal code, at most 64 bytes.
          example: "62701"
          maxLength: 64
        region:
          type: string
          description: State or region, at most 256 bytes.
          example: IL
          maxLength: 256
        street1:
          type: string
          description: Street line 1, at most 512 bytes.
          example: 100 Example Street
          maxLength: 512
        street2:
          type: string
          description: Street line 2, at most 512 bytes.
          example: Suite 200
          maxLength: 512
      x-validation-constraints:
        properties:
          street1:
            description: Street line 1, at most 512 bytes.
            example: 100 Example Street
            maxLength: 512
          street2:
            description: Street line 2, at most 512 bytes.
            example: Suite 200
            maxLength: 512
          city:
            description: City, at most 256 bytes.
            example: Springfield
            maxLength: 256
          region:
            description: State or region, at most 256 bytes.
            example: IL
            maxLength: 256
          postal_code:
            description: Postal code, at most 64 bytes.
            example: "62701"
            maxLength: 64
          country_code:
            description: ISO-3166 alpha-2 country code (at most 3 bytes).
            example: US
            maxLength: 3
    types.BusinessCollectionMove:
      type: object
      properties:
        collection_id:
          type: string
          description: Current collection ID, unique across moves, 1-256 bytes.
          example: "7123456789012352"
          minLength: 1
          maxLength: 256
        from_index:
          type: integer
          description: Current zero-based position (0-99).
          example: 1
          minimum: 0
          maximum: 99
        to_index:
          type: integer
          description: Target zero-based position (0-99).
          example: 0
          minimum: 0
          maximum: 99
      required:
        - collection_id
        - from_index
        - to_index
      x-validation-constraints:
        required:
          - collection_id
          - from_index
          - to_index
        properties:
          collection_id:
            description: Current collection ID, unique across moves, 1-256 bytes.
            example: "7123456789012352"
            minLength: 1
            maxLength: 256
          from_index:
            description: Current zero-based position (0-99).
            example: 1
            minimum: 0
            maximum: 99
          to_index:
            description: Target zero-based position (0-99).
            example: 0
            minimum: 0
            maximum: 99
    types.BusinessCollectionUpdate:
      type: object
      properties:
        add_product_ids:
          type: array
          items:
            type: string
          description: Own product IDs to add (0-100 unique, 1-256 bytes each). Must not repeat an ID from `remove_product_ids`.
          example:
            - "7123456789012346"
          maxItems: 100
          uniqueItems: true
        name:
          type: string
          description: New collection name, 1-256 bytes (trimmed). Omit to keep the current name.
          example: Dining chairs
          minLength: 1
          maxLength: 256
        remove_product_ids:
          type: array
          items:
            type: string
          description: Product IDs to remove from the collection (0-100 unique, 1-256 bytes each).
          example:
            - "7123456789012345"
          maxItems: 100
          uniqueItems: true
      x-validation-constraints:
        properties:
          name:
            description: New collection name, 1-256 bytes (trimmed). Omit to keep the current name.
            example: Dining chairs
            minLength: 1
            maxLength: 256
          add_product_ids:
            description: Own product IDs to add (0-100 unique, 1-256 bytes each). Must not repeat an ID from `remove_product_ids`.
            example:
              - "7123456789012346"
            maxItems: 100
            uniqueItems: true
          remove_product_ids:
            description: Product IDs to remove from the collection (0-100 unique, 1-256 bytes each).
            example:
              - "7123456789012345"
            maxItems: 100
            uniqueItems: true
    types.BusinessComplianceInfo:
      type: object
      properties:
        country_code_origin:
          type: string
          description: ISO-3166 alpha-2 country of origin (at most 3 bytes).
          example: BR
          maxLength: 3
        importer_address:
          allOf:
            - $ref: "#/components/schemas/types.BusinessAddress"
          description: Importer address, when the product is imported.
          example:
            street1: 100 Example Street
            city: Springfield
            region: IL
            postal_code: "62701"
            country_code: US
        importer_name:
          type: string
          description: Importer legal name, at most 256 bytes.
          example: Example Imports Ltd
          maxLength: 256
      x-validation-constraints:
        properties:
          country_code_origin:
            description: ISO-3166 alpha-2 country of origin (at most 3 bytes).
            example: BR
            maxLength: 3
          importer_name:
            description: Importer legal name, at most 256 bytes.
            example: Example Imports Ltd
            maxLength: 256
          importer_address:
            description: Importer address, when the product is imported.
            example:
              street1: 100 Example Street
              city: Springfield
              region: IL
              postal_code: "62701"
              country_code: US
    types.BusinessFeature:
      type: string
      enum:
        - meta_verified
        - marketing_messages
        - genai
        - genai_image
        - meta_one
        - bb_pro
      x-enum-varnames:
        - BusinessFeatureMetaVerified
        - BusinessFeatureMarketingMessages
        - BusinessFeatureGenAI
        - BusinessFeatureGenAIImage
        - BusinessFeatureMetaOne
        - BusinessFeatureBBPro
      description: "Business feature to check: `meta_verified`, `marketing_messages`, `genai`, `genai_image`, `meta_one` or `bb_pro`."
      example: meta_verified
      x-validation-constraints:
        description: "Business feature to check: `meta_verified`, `marketing_messages`, `genai`, `genai_image`, `meta_one` or `bb_pro`."
        example: meta_verified
    types.BusinessHoursDay:
      type: object
      properties:
        closeTime:
          type: integer
          description: Closing time in minutes after midnight (0-1439), different from openTime. Only with `specific_hours`.
          example: 1080
          minimum: 0
          maximum: 1439
        dayOfWeek:
          type: string
          description: Day of the week.
          enum:
            - sun
            - mon
            - tue
            - wed
            - thu
            - fri
            - sat
          example: mon
        mode:
          type: string
          description: "`specific_hours` uses openTime/closeTime; `open_24h` and `appointment_only` must not send them."
          enum:
            - specific_hours
            - open_24h
            - appointment_only
          example: specific_hours
        openTime:
          type: integer
          description: Opening time in minutes after midnight (0-1439). Only with `specific_hours`.
          example: 540
          minimum: 0
          maximum: 1439
      x-validation-constraints:
        properties:
          dayOfWeek:
            description: Day of the week.
            enum:
              - sun
              - mon
              - tue
              - wed
              - thu
              - fri
              - sat
            example: mon
          mode:
            description: "`specific_hours` uses openTime/closeTime; `open_24h` and `appointment_only` must not send them."
            enum:
              - specific_hours
              - open_24h
              - appointment_only
            example: specific_hours
          openTime:
            description: Opening time in minutes after midnight (0-1439). Only with `specific_hours`.
            example: 540
            minimum: 0
            maximum: 1439
          closeTime:
            description: Closing time in minutes after midnight (0-1439), different from openTime. Only with `specific_hours`.
            example: 1080
            minimum: 0
            maximum: 1439
    types.BusinessHoursUpdate:
      type: object
      properties:
        days:
          type: array
          items:
            $ref: "#/components/schemas/types.BusinessHoursDay"
          description: Up to 7 entries with unique `dayOfWeek`.
          example:
            - dayOfWeek: mon
              mode: specific_hours
              openTime: 540
              closeTime: 1080
          maxItems: 7
        timeZone:
          type: string
          description: IANA time zone of the schedule.
          example: America/Sao_Paulo
      x-validation-constraints:
        properties:
          timeZone:
            description: IANA time zone of the schedule.
            example: America/Sao_Paulo
          days:
            description: Up to 7 entries with unique `dayOfWeek`.
            example:
              - dayOfWeek: mon
                mode: specific_hours
                openTime: 540
                closeTime: 1080
            maxItems: 7
    types.BusinessMerchantCompliance:
      type: object
      properties:
        customer_care_details:
          allOf:
            - $ref: "#/components/schemas/types.BusinessMerchantContact"
          description: Customer care contact shown to buyers. Omitted fields are sent as empty strings.
          example:
            email: support@example.com
            landline_number: ""
            mobile_number: "5511999999999"
        entity_name:
          type: string
          description: Registered legal name, required, 1-256 bytes after trimming.
          example: Example Store Ltd
          minLength: 1
          maxLength: 256
        entity_type:
          allOf:
            - $ref: "#/components/schemas/types.BusinessMerchantEntityType"
          description: Legal entity type. `OTHER` requires `entity_type_custom`.
          example: PRIVATE_COMPANY
        entity_type_custom:
          type: string
          description: Custom entity type, required when `entity_type` is `OTHER` (at most 256 bytes).
          example: Cooperative
          maxLength: 256
        grievance_officer_details:
          allOf:
            - $ref: "#/components/schemas/types.BusinessMerchantOfficer"
          description: Grievance officer contact. Omitted fields are sent as empty strings.
          example:
            name: Jane Doe
            email: compliance@example.com
            landline_number: ""
            mobile_number: "5511988888888"
        is_registered:
          type: boolean
          description: Whether the business is formally registered.
          example: true
      required:
        - entity_name
        - entity_type
      x-validation-constraints:
        required:
          - entity_name
          - entity_type
        properties:
          entity_name:
            description: Registered legal name, required, 1-256 bytes after trimming.
            example: Example Store Ltd
            minLength: 1
            maxLength: 256
          entity_type:
            description: Legal entity type. `OTHER` requires `entity_type_custom`.
            example: PRIVATE_COMPANY
          entity_type_custom:
            description: Custom entity type, required when `entity_type` is `OTHER` (at most 256 bytes).
            example: Cooperative
            maxLength: 256
          is_registered:
            description: Whether the business is formally registered.
            example: true
          customer_care_details:
            description: Customer care contact shown to buyers. Omitted fields are sent as empty strings.
            example:
              email: support@example.com
              landline_number: ""
              mobile_number: "5511999999999"
          grievance_officer_details:
            description: Grievance officer contact. Omitted fields are sent as empty strings.
            example:
              name: Jane Doe
              email: compliance@example.com
              landline_number: ""
              mobile_number: "5511988888888"
    types.BusinessMerchantContact:
      type: object
      properties:
        email:
          type: string
          description: Contact email, at most 254 bytes.
          example: support@example.com
          maxLength: 254
        landline_number:
          type: string
          description: Landline number, at most 64 bytes. Empty string when not set.
          example: ""
          maxLength: 64
        mobile_number:
          type: string
          description: Mobile number, at most 64 bytes.
          example: "5511999999999"
          maxLength: 64
      x-validation-constraints:
        properties:
          email:
            description: Contact email, at most 254 bytes.
            example: support@example.com
            maxLength: 254
          landline_number:
            description: Landline number, at most 64 bytes. Empty string when not set.
            example: ""
            maxLength: 64
          mobile_number:
            description: Mobile number, at most 64 bytes.
            example: "5511999999999"
            maxLength: 64
    types.BusinessMerchantEntityType:
      type: string
      enum:
        - SOLE_PROPRIETORSHIP
        - PARTNERSHIP
        - PRIVATE_COMPANY
        - PUBLIC_COMPANY
        - LIMITED_LIABILITY_PARTNERSHIP
        - OTHER
      x-enum-varnames:
        - BusinessMerchantEntitySoleProprietorship
        - BusinessMerchantEntityPartnership
        - BusinessMerchantEntityPrivateCompany
        - BusinessMerchantEntityPublicCompany
        - BusinessMerchantEntityLimitedLiabilityPartnership
        - BusinessMerchantEntityOther
      description: Legal entity type of the merchant.
      example: PRIVATE_COMPANY
      x-validation-constraints:
        description: Legal entity type of the merchant.
        example: PRIVATE_COMPANY
    types.BusinessMerchantOfficer:
      type: object
      properties:
        email:
          type: string
          description: Officer email, at most 254 bytes.
          example: compliance@example.com
          maxLength: 254
        landline_number:
          type: string
          description: Landline number, at most 64 bytes. Empty string when not set.
          example: ""
          maxLength: 64
        mobile_number:
          type: string
          description: Mobile number, at most 64 bytes.
          example: "5511988888888"
          maxLength: 64
        name:
          type: string
          description: Officer name, at most 256 bytes.
          example: Jane Doe
          maxLength: 256
      x-validation-constraints:
        properties:
          name:
            description: Officer name, at most 256 bytes.
            example: Jane Doe
            maxLength: 256
          email:
            description: Officer email, at most 254 bytes.
            example: compliance@example.com
            maxLength: 254
          landline_number:
            description: Landline number, at most 64 bytes. Empty string when not set.
            example: ""
            maxLength: 64
          mobile_number:
            description: Mobile number, at most 64 bytes.
            example: "5511988888888"
            maxLength: 64
    types.BusinessProductInput:
      type: object
      properties:
        compliance_category:
          type: string
          description: Set `COUNTRY_ORIGIN_EXEMPT` (without `compliance_info`) when a country of origin does not apply. At most 128 bytes.
          example: COUNTRY_ORIGIN_EXEMPT
          maxLength: 128
        compliance_info:
          allOf:
            - $ref: "#/components/schemas/types.BusinessComplianceInfo"
          description: Country of origin and optional importer. Optional in the API; omitting it on PUT removes the country of origin.
          example:
            country_code_origin: BR
            importer_name: Example Imports Ltd
        currency:
          type: string
          description: ISO-4217 uppercase code. Required with `price`; forbidden without it.
          example: BRL
          minLength: 3
          maxLength: 3
        description:
          type: string
          description: Product description, at most 4096 bytes.
          example: Solid wood chair with natural finish.
          maxLength: 4096
        image_urls:
          type: array
          items:
            type: string
          description: 1-10 HTTPS WhatsApp/Meta media URLs (`*.whatsapp.net`, `*.fbcdn.net`, `*.facebook.com`), normally the `image_id` returned by `POST /business/products/image`. The first one is the main photo.
          example:
            - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
          minItems: 1
          maxItems: 10
        is_hidden:
          type: boolean
          description: True creates or keeps the product hidden. Always sent on PUT, so omitting it makes the product visible.
          example: false
        name:
          type: string
          description: Product name, required, 1-256 bytes after trimming (`400 product name must contain 1-256 bytes`).
          example: Wooden chair
          minLength: 1
          maxLength: 256
        price:
          type: string
          description: Price as a string with integer thousandths of the currency, no separators (`"3000000"` = 3,000.00). Up to 18 digits.
          example: "3000000"
        retailer_id:
          type: string
          description: Seller SKU / retailer ID, at most 256 bytes.
          example: CHAIR-001
          maxLength: 256
        sale_price:
          type: string
          description: Sale price in thousandths of the currency. Requires `price`; omitting it on PUT removes the sale price.
          example: "2499900"
        url:
          type: string
          description: "Product page: absolute HTTPS URL, at most 2048 bytes."
          example: https://example.com/products/chair
          maxLength: 2048
        video_urls:
          type: array
          items:
            type: string
          description: Up to 10 HTTPS WhatsApp/Meta media URLs of product videos.
          example:
            - https://mmg.whatsapp.net/v/t42.1790-2/example-video.mp4
          maxItems: 10
      required:
        - name
        - image_urls
      x-validation-constraints:
        required:
          - name
          - image_urls
        properties:
          name:
            description: Product name, required, 1-256 bytes after trimming (`400 product name must contain 1-256 bytes`).
            example: Wooden chair
            minLength: 1
            maxLength: 256
          description:
            description: Product description, at most 4096 bytes.
            example: Solid wood chair with natural finish.
            maxLength: 4096
          currency:
            description: ISO-4217 uppercase code. Required with `price`; forbidden without it.
            example: BRL
            minLength: 3
            maxLength: 3
          price:
            description: Price as a string with integer thousandths of the currency, no separators (`"3000000"` = 3,000.00). Up to 18 digits.
            example: "3000000"
          sale_price:
            description: Sale price in thousandths of the currency. Requires `price`; omitting it on PUT removes the sale price.
            example: "2499900"
          url:
            description: "Product page: absolute HTTPS URL, at most 2048 bytes."
            example: https://example.com/products/chair
            maxLength: 2048
          retailer_id:
            description: Seller SKU / retailer ID, at most 256 bytes.
            example: CHAIR-001
            maxLength: 256
          is_hidden:
            description: True creates or keeps the product hidden. Always sent on PUT, so omitting it makes the product visible.
            example: false
          image_urls:
            description: 1-10 HTTPS WhatsApp/Meta media URLs (`*.whatsapp.net`, `*.fbcdn.net`, `*.facebook.com`), normally the `image_id` returned by `POST /business/products/image`. The first one is the main photo.
            example:
              - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
            minItems: 1
            maxItems: 10
          video_urls:
            description: Up to 10 HTTPS WhatsApp/Meta media URLs of product videos.
            example:
              - https://mmg.whatsapp.net/v/t42.1790-2/example-video.mp4
            maxItems: 10
          compliance_category:
            description: Set `COUNTRY_ORIGIN_EXEMPT` (without `compliance_info`) when a country of origin does not apply. At most 128 bytes.
            example: COUNTRY_ORIGIN_EXEMPT
            maxLength: 128
          compliance_info:
            description: Country of origin and optional importer. Optional in the API; omitting it on PUT removes the country of origin.
            example:
              country_code_origin: BR
              importer_name: Example Imports Ltd
    types.BusinessProfileUpdate:
      type: object
      properties:
        address:
          type: string
          description: Business address, at most 512 bytes. Omit or `null` to keep it unchanged.
          example: 100 Example Street, Springfield, IL
          maxLength: 512
        description:
          type: string
          description: Business description, at most 1024 bytes. Omit or `null` to keep it unchanged.
          example: Handmade wooden furniture.
          maxLength: 1024
        email:
          type: string
          description: Contact email, at most 320 bytes, plain address only; an empty string clears it. Omit or `null` to keep it unchanged.
          example: contact@example.com
          maxLength: 320
        hours:
          allOf:
            - $ref: "#/components/schemas/types.BusinessHoursUpdate"
          description: Weekly business hours. Replaces the configured schedule when sent.
          example:
            timeZone: America/Sao_Paulo
            days:
              - dayOfWeek: mon
                mode: specific_hours
                openTime: 540
                closeTime: 1080
        websites:
          type: array
          items:
            type: string
          description: Up to 2 absolute http/https URLs (at most 2048 bytes each); `[]` clears them. Omit or `null` to keep them unchanged.
          example:
            - https://example.com
          maxItems: 2
      x-validation-constraints:
        properties:
          address:
            description: Business address, at most 512 bytes. Omit or `null` to keep it unchanged.
            example: 100 Example Street, Springfield, IL
            maxLength: 512
          description:
            description: Business description, at most 1024 bytes. Omit or `null` to keep it unchanged.
            example: Handmade wooden furniture.
            maxLength: 1024
          email:
            description: Contact email, at most 320 bytes, plain address only; an empty string clears it. Omit or `null` to keep it unchanged.
            example: contact@example.com
            maxLength: 320
          websites:
            description: Up to 2 absolute http/https URLs (at most 2048 bytes each); `[]` clears them. Omit or `null` to keep them unchanged.
            example:
              - https://example.com
            maxItems: 2
          hours:
            description: Weekly business hours. Replaces the configured schedule when sent.
            example:
              timeZone: America/Sao_Paulo
              days:
                - dayOfWeek: mon
                  mode: specific_hours
                  openTime: 540
                  closeTime: 1080
    types.ContactQRLinkTarget:
      type: object
      properties:
        jid:
          description: The JID of the user.
          allOf:
            - $ref: "#/components/schemas/types.JID"
        pushName:
          description: The notify / push name of the user.
          type: string
        type:
          description: Might always be "contact".
          type: string
    types.JID:
      type: object
      properties:
        device:
          type: integer
        integrator:
          type: integer
        rawAgent:
          type: integer
        server:
          type: string
        user:
          type: string
    types.UsernameResolution:
      type: object
      properties:
        keyRequired:
          type: boolean
        lid:
          $ref: "#/components/schemas/types.JID"
        username:
          type: string
    voip.CallLinkInfo:
      type: object
      properties:
        token:
          type: string
        url:
          type: string
        video:
          type: boolean
    voip.CallLinkPreviewInfo:
      type: object
      properties:
        approvalRequired:
          type: boolean
        creator:
          type: string
        creatorPhone:
          type: string
        isAdmin:
          type: boolean
        token:
          type: string
        video:
          type: boolean
    voip.GroupCallInfo:
      type: object
      properties:
        callId:
          type: string
        participants:
          type: array
          items:
            $ref: "#/components/schemas/voip.GroupParticipantInfo"
        transactionId:
          type: integer
    voip.GroupParticipantInfo:
      type: object
      properties:
        jid:
          type: string
        phone:
          type: string
        state:
          type: string
    voip.WaitingRoomInfo:
      type: object
      properties:
        callId:
          type: string
        enabled:
          type: boolean
        inWaitingRoom:
          type: boolean
        isAdmin:
          type: boolean
        transactionId:
          type: integer
        users:
          type: array
          items:
            $ref: "#/components/schemas/voip.WaitingRoomUser"
    voip.WaitingRoomUser:
      type: object
      properties:
        jid:
          type: string
        phone:
          type: string
    whatsmeow.IdentityVerificationCodes:
      type: object
      properties:
        displayQRCode:
          type: array
          items:
            type: integer
        numericCode:
          type: string
        phoneNumber:
          $ref: "#/components/schemas/types.JID"
        userID:
          $ref: "#/components/schemas/types.JID"
        username:
          type: string
        verificationQRCode:
          type: array
          items:
            type: integer
    main.BusinessOrderRecoveryResponse:
      type: object
      properties:
        error:
          type: string
        message_delivery:
          type: string
          enum:
            - unknown
            - not_attempted
        message_sent:
          type: boolean
          x-nullable: true
        order_created:
          type: boolean
        order_exists:
          type: boolean
        order_reused:
          type: boolean
        registered_order:
          $ref: "#/components/schemas/whatsmeow.BusinessCreatedOrder"
        success:
          type: boolean
    main.BusinessOrderSendItem:
      type: object
      properties:
        currency:
          type: string
          description: ISO-4217 uppercase currency; all lines must match with `create_order`.
          example: BRL
        name:
          type: string
          description: Product name (1-256 bytes with `create_order`).
          example: Wooden chair
        price:
          type: string
          description: Legacy free-form price text; ignored by `create_order`, which uses `price_amount1000`.
          example: "3000.00"
        price_amount1000:
          type: integer
          description: Unit price in integer thousandths of the currency, required and non-negative with `create_order`.
          example: 3000000
        product_id:
          type: string
          description: Catalog product ID of the seller (1-256 bytes with `create_order`).
          example: "7123456789012345"
        quantity:
          type: integer
          description: Quantity (1-100 with `create_order`; at most 100 items in total).
          example: 1
      x-validation-constraints:
        properties:
          product_id:
            description: Catalog product ID of the seller (1-256 bytes with `create_order`).
            example: "7123456789012345"
          name:
            description: Product name (1-256 bytes with `create_order`).
            example: Wooden chair
          quantity:
            description: Quantity (1-100 with `create_order`; at most 100 items in total).
            example: 1
          price:
            description: Legacy free-form price text; ignored by `create_order`, which uses `price_amount1000`.
            example: "3000.00"
          currency:
            description: ISO-4217 uppercase currency; all lines must match with `create_order`.
            example: BRL
          price_amount1000:
            description: Unit price in integer thousandths of the currency, required and non-negative with `create_order`.
            example: 3000000
    main.BusinessOrderSendRequest:
      type: object
      properties:
        business_jid:
          description: Seller business JID (phone@s.whatsapp.net); the order message is sent to this chat.
          type: string
          example: 5511988888888@s.whatsapp.net
        create_order:
          description: Register a real catalog order before sending; a 502 with order_created=true must not be retried as a new order.
          type: boolean
          example: true
        order_id:
          description: "Order ID: free-form in legacy mode, must be empty with create_order, required with resend_registered_order."
          type: string
          example: "1976123456789012"
        order_items:
          description: Order lines; required unless resend_registered_order. create_order needs 1-100 unique lines with price_amount1000 and one currency.
          type: array
          items:
            $ref: "#/components/schemas/main.BusinessOrderSendItem"
          example:
            - product_id: "7123456789012345"
              name: Wooden chair
              quantity: 1
              currency: BRL
              price_amount1000: 3000000
        order_message:
          description: Optional text shown in the order message.
          type: string
          example: Order for one wooden chair
        order_token:
          description: OrderToken is an authenticated recovery secret, accepted in JSON only and never logged.
          type: string
          example: AR6example0rderT0kenBase64==
        resend_registered_order:
          description: ResendRegisteredOrder queries an existing order and sends it once without creating another. Mutually exclusive with CreateOrder.
          type: boolean
          example: false
      required:
        - business_jid
      x-validation-constraints:
        required:
          - business_jid
        properties:
          business_jid:
            example: 5511988888888@s.whatsapp.net
          order_items:
            example:
              - product_id: "7123456789012345"
                name: Wooden chair
                quantity: 1
                currency: BRL
                price_amount1000: 3000000
          order_message:
            example: Order for one wooden chair
          order_id:
            example: "1976123456789012"
          create_order:
            example: true
          resend_registered_order:
            example: false
          order_token:
            example: AR6example0rderT0kenBase64==
    main.BusinessOrderSendResponse:
      type: object
      properties:
        business_jid:
          type: string
        message:
          type: string
        message_id:
          type: string
        order_created:
          type: boolean
        order_details:
          type: object
          properties:
            item_count:
              type: integer
            order_id:
              type: string
        order_kind:
          type: string
          enum:
            - legacy_summary
            - registered_order
        registered_order:
          $ref: "#/components/schemas/whatsmeow.BusinessCreatedOrder"
        success:
          type: boolean
        timestamp:
          type: string
    main.GroupHistoryShareModeRequest:
      type: object
      required:
        - groupjid
        - mode
      properties:
        groupjid:
          description: Group JID (@g.us, no device) to configure.
          type: string
          example: 120363000000000011@g.us
        mode:
          description: "Who may share recent history: admin_share (admins only) or all_member_share (all members)."
          type: string
          enum:
            - admin_share
            - all_member_share
          example: admin_share
    whatsmeow.BusinessCreatedOrder:
      type: object
      properties:
        order_id:
          type: string
        price:
          type: object
          properties:
            currency:
              type: string
            price_status:
              type: string
            subtotal_amount:
              type: string
            total_amount:
              type: string
        token:
          type: string
    main.BusinessBillingSendRequest:
      type: object
      properties:
        billing:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingParams"
          description: "Charge and accept only: explicit billing payload; forbidden in status and payment modes."
          example:
            reference_id: REF-0001
            type: physical-goods
            currency: BRL
            total_amount:
              value: 15000
              offset: 100
            order:
              status: payment_requested
              order_type: ORDER
              items:
                - product_id: "7123456789012345"
                  name: Wooden chair
                  quantity: 1
                  amount:
                    value: 15000
                    offset: 100
              subtotal:
                value: 15000
                offset: 100
            payment_settings:
              - type: pix_static_code
                pix_static_code:
                  key: payments@example.com
                  key_type: EMAIL
                  merchant_name: Example Store
        billing_message_id:
          description: "Status and payment only: ID of the own billing message to quote."
          type: string
          example: 3EB0C767D26A1D6B8A9F
        body:
          description: Optional message text, at most 4096 bytes.
          type: string
          example: Your order is ready for payment.
          maxLength: 4096
        id:
          description: Optional custom WhatsApp message ID, at most 128 bytes.
          type: string
          example: 3EB0C767D26A1D6B8A90
          maxLength: 128
        image:
          description: "Charge only: optional JPEG/PNG base64 or data URI turned into a JPEG header thumbnail; forbidden elsewhere."
          type: string
          example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
        incoming_message_id:
          description: "Accept only: ID of the received order message stored by this instance; forbidden in other modes."
          type: string
          example: 3EB0C767D26A1D6B8A9F
        order_status:
          description: "Payment mode only: optional order lifecycle status to declare with the payment."
          type: string
          example: processing
        payment_method:
          description: "Payment mode only: declared payment method, at most 64 bytes."
          type: string
          example: pix
          maxLength: 64
        payment_timestamp:
          description: "Payment mode only: positive payment timestamp; payment_method or payment_timestamp is required."
          type: integer
          example: 1757937600
          minimum: 1
        reference_id:
          description: "Status and payment only: reference_id of the quoted billing."
          type: string
          example: REF-0001
        status:
          description: Order status (status mode) or payment status pending|captured|failed (payment mode).
          type: string
          example: shipped
        to:
          description: Required buyer JID, individual @s.whatsapp.net or @lid without device.
          type: string
          example: 5511999999999@s.whatsapp.net
      required:
        - to
      x-validation-constraints:
        required:
          - to
        properties:
          to:
            example: 5511999999999@s.whatsapp.net
          id:
            example: 3EB0C767D26A1D6B8A90
            maxLength: 128
          body:
            example: Your order is ready for payment.
            maxLength: 4096
          image:
            example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=
          incoming_message_id:
            example: 3EB0C767D26A1D6B8A9F
          billing_message_id:
            example: 3EB0C767D26A1D6B8A9F
          reference_id:
            example: REF-0001
          status:
            example: shipped
          payment_method:
            example: pix
            maxLength: 64
          payment_timestamp:
            example: 1757937600
            minimum: 1
          order_status:
            example: processing
          billing:
            description: "Charge and accept only: explicit billing payload; forbidden in status and payment modes."
            example:
              reference_id: REF-0001
              type: physical-goods
              currency: BRL
              total_amount:
                value: 15000
                offset: 100
              order:
                status: payment_requested
                order_type: ORDER
                items:
                  - product_id: "7123456789012345"
                    name: Wooden chair
                    quantity: 1
                    amount:
                      value: 15000
                      offset: 100
                subtotal:
                  value: 15000
                  offset: 100
              payment_settings:
                - type: pix_static_code
                  pix_static_code:
                    key: payments@example.com
                    key_type: EMAIL
                    merchant_name: Example Store
    whatsmeow.BusinessBillingAmount:
      type: object
      properties:
        offset:
          type: integer
          description: Positive divisor for `value` (use 100 for cents, 1000 for thousandths).
          example: 100
          minimum: 1
        value:
          type: integer
          description: Non-negative integer numerator; the amount is `value / offset` (`15000/100` = 150.00).
          example: 15000
          minimum: 0
      required:
        - value
        - offset
      x-validation-constraints:
        required:
          - value
          - offset
        properties:
          value:
            description: Non-negative integer numerator; the amount is `value / offset` (`15000/100` = 150.00).
            example: 15000
            minimum: 0
          offset:
            description: Positive divisor for `value` (use 100 for cents, 1000 for thousandths).
            example: 100
            minimum: 1
    whatsmeow.BusinessBillingCards:
      type: object
      properties:
        enabled:
          type: boolean
          description: Whether card payment is offered.
          example: true
      required:
        - enabled
      description: "`enabled` must be present; false is valid. Does not execute a payment."
      x-validation-constraints:
        required:
          - enabled
        description: "`enabled` must be present; false is valid. Does not execute a payment."
        properties:
          enabled:
            description: Whether card payment is offered.
            example: true
    whatsmeow.BusinessBillingExternalPayment:
      type: object
      properties:
        payment_instruction:
          type: string
          maxLength: 1024
          x-maxBytes: 1024
          minLength: 1
          description: Non-empty instruction text, at most 1024 UTF-8 bytes.
          example: Pay at https://example.com/pay/REF-0001
        type:
          type: string
          maxLength: 64
          x-maxBytes: 64
          minLength: 1
          example: payment_instruction
          description: Instruction type; the observed value is `payment_instruction` (other strings are not proven to render).
      required:
        - type
        - payment_instruction
      x-validation-constraints:
        required:
          - type
          - payment_instruction
        properties:
          type:
            maxLength: 64
            x-maxBytes: 64
            minLength: 1
            example: payment_instruction
            description: Instruction type; the observed value is `payment_instruction` (other strings are not proven to render).
          payment_instruction:
            maxLength: 1024
            x-maxBytes: 1024
            minLength: 1
            description: Non-empty instruction text, at most 1024 UTF-8 bytes.
            example: Pay at https://example.com/pay/REF-0001
    whatsmeow.BusinessBillingItem:
      type: object
      properties:
        amount:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingAmount"
          description: Unit amount.
          example:
            value: 15000
            offset: 100
        isCustomItem:
          type: boolean
          description: True for an item that is not in the catalog; requires `retailer_id` starting with `custom-item` and no `product_id`.
          example: false
        isQuantitySet:
          type: boolean
          description: Optional flag forwarded as-is in the billing payload.
          example: true
        name:
          type: string
          description: Item name, 1-256 bytes.
          example: Wooden chair
          minLength: 1
          maxLength: 256
        product_id:
          type: string
          description: Catalog product ID (at most 256 bytes). `product_id` or `retailer_id` is required.
          example: "7123456789012345"
          maxLength: 256
        quantity:
          type: integer
          description: Quantity, 1-100.
          example: 1
          minimum: 1
          maximum: 100
        retailer_id:
          type: string
          description: Retailer ID (at most 256 bytes); custom items must start with `custom-item`.
          example: CHAIR-001
          maxLength: 256
      required:
        - name
        - quantity
        - amount
      x-validation-constraints:
        required:
          - name
          - quantity
          - amount
        properties:
          isCustomItem:
            description: True for an item that is not in the catalog; requires `retailer_id` starting with `custom-item` and no `product_id`.
            example: false
          isQuantitySet:
            description: Optional flag forwarded as-is in the billing payload.
            example: true
          product_id:
            description: Catalog product ID (at most 256 bytes). `product_id` or `retailer_id` is required.
            example: "7123456789012345"
            maxLength: 256
          retailer_id:
            description: Retailer ID (at most 256 bytes); custom items must start with `custom-item`.
            example: CHAIR-001
            maxLength: 256
          name:
            description: Item name, 1-256 bytes.
            example: Wooden chair
            minLength: 1
            maxLength: 256
          quantity:
            description: Quantity, 1-100.
            example: 1
            minimum: 1
            maximum: 100
          amount:
            description: Unit amount.
            example:
              value: 15000
              offset: 100
    whatsmeow.BusinessBillingOrder:
      type: object
      properties:
        discount:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingAmount"
          description: Optional discount, not larger than the subtotal.
          example:
            value: 3000
            offset: 100
        items:
          type: array
          items:
            $ref: "#/components/schemas/whatsmeow.BusinessBillingItem"
          description: 1-100 items.
          example:
            - product_id: "7123456789012345"
              name: Wooden chair
              quantity: 1
              amount:
                value: 15000
                offset: 100
          minItems: 1
          maxItems: 100
        order_type:
          type: string
          description: Order type; must be `ORDER`.
          enum:
            - ORDER
          example: ORDER
        shipping:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingAmount"
          description: Optional shipping amount.
          example:
            value: 2000
            offset: 100
        status:
          type: string
          description: Initial order status; must be `payment_requested`.
          enum:
            - payment_requested
          example: payment_requested
        subtotal:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingAmount"
          description: Must equal the sum of quantity × amount of all items.
          example:
            value: 15000
            offset: 100
        tax:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingAmount"
          description: Optional tax amount.
          example:
            value: 0
            offset: 100
      required:
        - status
        - order_type
        - items
        - subtotal
      x-validation-constraints:
        required:
          - status
          - order_type
          - items
          - subtotal
        properties:
          status:
            description: Initial order status; must be `payment_requested`.
            enum:
              - payment_requested
            example: payment_requested
          order_type:
            description: Order type; must be `ORDER`.
            enum:
              - ORDER
            example: ORDER
          items:
            description: 1-100 items.
            example:
              - product_id: "7123456789012345"
                name: Wooden chair
                quantity: 1
                amount:
                  value: 15000
                  offset: 100
            minItems: 1
            maxItems: 100
          subtotal:
            description: Must equal the sum of quantity × amount of all items.
            example:
              value: 15000
              offset: 100
          discount:
            description: Optional discount, not larger than the subtotal.
            example:
              value: 3000
              offset: 100
          shipping:
            description: Optional shipping amount.
            example:
              value: 2000
              offset: 100
          tax:
            description: Optional tax amount.
            example:
              value: 0
              offset: 100
    whatsmeow.BusinessBillingPIX:
      type: object
      properties:
        flow_type:
          type: string
          maxLength: 64
          x-maxBytes: 64
          description: Optional flow type, omitted when empty; at most 64 UTF-8 bytes.
          example: static
        key:
          type: string
          maxLength: 256
          x-maxBytes: 256
          minLength: 1
          description: PIX key, required, non-empty after trimming, at most 256 UTF-8 bytes.
          example: payments@example.com
        key_type:
          type: string
          maxLength: 64
          x-maxBytes: 64
          minLength: 1
          description: PIX key type such as `EMAIL`, `PHONE`, `CPF`, `CNPJ` or `EVP`; required, at most 64 UTF-8 bytes.
          example: EMAIL
        merchant_name:
          type: string
          maxLength: 256
          x-maxBytes: 256
          minLength: 1
          description: Merchant name shown to the buyer, required, at most 256 UTF-8 bytes.
          example: Example Store
      required:
        - key
        - key_type
        - merchant_name
      x-validation-constraints:
        required:
          - key
          - key_type
          - merchant_name
        properties:
          key:
            maxLength: 256
            x-maxBytes: 256
            minLength: 1
            description: PIX key, required, non-empty after trimming, at most 256 UTF-8 bytes.
            example: payments@example.com
          key_type:
            maxLength: 64
            x-maxBytes: 64
            minLength: 1
            description: PIX key type such as `EMAIL`, `PHONE`, `CPF`, `CNPJ` or `EVP`; required, at most 64 UTF-8 bytes.
            example: EMAIL
          flow_type:
            maxLength: 64
            x-maxBytes: 64
            description: Optional flow type, omitted when empty; at most 64 UTF-8 bytes.
            example: static
          merchant_name:
            maxLength: 256
            x-maxBytes: 256
            minLength: 1
            description: Merchant name shown to the buyer, required, at most 256 UTF-8 bytes.
            example: Example Store
    whatsmeow.BusinessBillingParams:
      type: object
      properties:
        additional_note:
          type: string
          description: Optional note, at most 4096 bytes.
          example: Delivery in 5 business days.
          maxLength: 4096
        currency:
          type: string
          description: Three uppercase letters (ISO-4217).
          example: BRL
          minLength: 3
          maxLength: 3
        external_payment_configurations:
          type: array
          items:
            $ref: "#/components/schemas/whatsmeow.BusinessBillingExternalPayment"
          maxItems: 5
          description: Up to 5 external payment instructions.
          example:
            - type: payment_instruction
              payment_instruction: Pay at https://example.com/pay/REF-0001
        order:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingOrder"
          description: Order with items and totals.
          example:
            status: payment_requested
            order_type: ORDER
            items:
              - product_id: "7123456789012345"
                name: Wooden chair
                quantity: 1
                amount:
                  value: 15000
                  offset: 100
            subtotal:
              value: 15000
              offset: 100
        order_request_id:
          type: string
          description: "Must NOT be sent: on accept it is derived from the verified incoming order (`400` otherwise)."
          example: ""
        payment_settings:
          type: array
          items:
            $ref: "#/components/schemas/whatsmeow.BusinessBillingPaymentSetting"
          maxItems: 2
          description: PIX and/or cards settings; no repeated types.
          example:
            - type: pix_static_code
              pix_static_code:
                key: payments@example.com
                key_type: EMAIL
                merchant_name: Example Store
        reference_id:
          type: string
          description: Seller reference, 1-256 bytes. Reused later by status and payment declarations.
          example: REF-0001
          minLength: 1
          maxLength: 256
        total_amount:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingAmount"
          description: Must equal subtotal − discount + shipping + tax.
          example:
            value: 15000
            offset: 100
        type:
          type: string
          description: Billing type; must be `physical-goods`.
          enum:
            - physical-goods
          example: physical-goods
      description: Billing (review and pay) payload. At least one payment configuration across the two arrays; does not execute a payment.
      required:
        - reference_id
        - type
        - currency
        - total_amount
        - order
      anyOf:
        - required:
            - payment_settings
          properties:
            payment_settings:
              minItems: 1
        - required:
            - external_payment_configurations
          properties:
            external_payment_configurations:
              minItems: 1
      x-validation-constraints:
        description: Billing (review and pay) payload. At least one payment configuration across the two arrays; does not execute a payment.
        required:
          - reference_id
          - type
          - currency
          - total_amount
          - order
        anyOf:
          - required:
              - payment_settings
            properties:
              payment_settings:
                minItems: 1
          - required:
              - external_payment_configurations
            properties:
              external_payment_configurations:
                minItems: 1
        properties:
          reference_id:
            description: Seller reference, 1-256 bytes. Reused later by status and payment declarations.
            example: REF-0001
            minLength: 1
            maxLength: 256
          order_request_id:
            description: "Must NOT be sent: on accept it is derived from the verified incoming order (`400` otherwise)."
            example: ""
          type:
            description: Billing type; must be `physical-goods`.
            enum:
              - physical-goods
            example: physical-goods
          currency:
            description: Three uppercase letters (ISO-4217).
            example: BRL
            minLength: 3
            maxLength: 3
          total_amount:
            description: Must equal subtotal − discount + shipping + tax.
            example:
              value: 15000
              offset: 100
          order:
            description: Order with items and totals.
            example:
              status: payment_requested
              order_type: ORDER
              items:
                - product_id: "7123456789012345"
                  name: Wooden chair
                  quantity: 1
                  amount:
                    value: 15000
                    offset: 100
              subtotal:
                value: 15000
                offset: 100
          additional_note:
            description: Optional note, at most 4096 bytes.
            example: Delivery in 5 business days.
            maxLength: 4096
          payment_settings:
            maxItems: 2
            description: PIX and/or cards settings; no repeated types.
            example:
              - type: pix_static_code
                pix_static_code:
                  key: payments@example.com
                  key_type: EMAIL
                  merchant_name: Example Store
          external_payment_configurations:
            maxItems: 5
            description: Up to 5 external payment instructions.
            example:
              - type: payment_instruction
                payment_instruction: Pay at https://example.com/pay/REF-0001
    whatsmeow.BusinessBillingPaymentSetting:
      type: object
      properties:
        cards:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingCards"
          description: Card settings (type `cards` only).
          example:
            enabled: true
        pix_static_code:
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessBillingPIX"
          description: PIX static code settings (type `pix_static_code` only).
          example:
            key: payments@example.com
            key_type: EMAIL
            merchant_name: Example Store
        type:
          type: string
          enum:
            - pix_static_code
            - cards
          description: "`pix_static_code` requires `pix_static_code`; `cards` requires `cards`. Each type at most once."
          example: pix_static_code
      required:
        - type
      oneOf:
        - required:
            - pix_static_code
          properties:
            type:
              enum:
                - pix_static_code
          not:
            required:
              - cards
            properties:
              cards:
                type: object
        - required:
            - cards
          properties:
            type:
              enum:
                - cards
          not:
            required:
              - pix_static_code
            properties:
              pix_static_code:
                type: object
      x-validation-constraints:
        required:
          - type
        properties:
          type:
            enum:
              - pix_static_code
              - cards
            description: "`pix_static_code` requires `pix_static_code`; `cards` requires `cards`. Each type at most once."
            example: pix_static_code
          pix_static_code:
            description: PIX static code settings (type `pix_static_code` only).
            example:
              key: payments@example.com
              key_type: EMAIL
              merchant_name: Example Store
          cards:
            description: Card settings (type `cards` only).
            example:
              enabled: true
        oneOf:
          - required:
              - pix_static_code
            properties:
              type:
                enum:
                  - pix_static_code
            not:
              required:
                - cards
              properties:
                cards:
                  type: object
          - required:
              - cards
            properties:
              type:
                enum:
                  - cards
            not:
              required:
                - pix_static_code
              properties:
                pix_static_code:
                  type: object
    main.BusinessOrderRejectRequest:
      type: object
      properties:
        body:
          description: Optional note appended after the refusal text, at most 4096 bytes.
          type: string
          example: Item out of stock.
          maxLength: 4096
        id:
          description: Optional custom WhatsApp message ID, at most 128 bytes.
          type: string
          example: 3EB0C767D26A1D6B8A90
          maxLength: 128
        incoming_message_id:
          description: Required ID of the received order message stored by this instance (max 128 bytes).
          type: string
          example: 3EB0C767D26A1D6B8A9F
          minLength: 1
          maxLength: 128
        to:
          description: Required buyer JID, individual @s.whatsapp.net or @lid without device.
          type: string
          example: 5511999999999@s.whatsapp.net
      required:
        - to
        - incoming_message_id
      x-validation-constraints:
        required:
          - to
          - incoming_message_id
        properties:
          to:
            example: 5511999999999@s.whatsapp.net
          incoming_message_id:
            example: 3EB0C767D26A1D6B8A9F
            minLength: 1
            maxLength: 128
          id:
            example: 3EB0C767D26A1D6B8A90
            maxLength: 128
          body:
            example: Item out of stock.
            maxLength: 4096
    main.GroupHistorySendRequest:
      type: object
      required:
        - bundle_id
        - group_jid
        - message_ids
        - notice_id
        - recipients
      properties:
        bundle_id:
          description: Unused message ID for the history bundle (max 128 chars, differs from notice_id, never reusable).
          type: string
          maxLength: 128
          example: 3EB0C767D26A1D6B8AA0
        group_jid:
          description: Group JID (@g.us, no device) whose retained messages are shared.
          type: string
          example: 120363000000000011@g.us
        message_ids:
          description: 1-100 distinct IDs (max 128 chars) of original messages retained for this user and group.
          type: array
          items:
            type: string
          example:
            - 3EB0C767D26A1D6B8A9F
            - 3EB0C767D26A1D6B8A9E
        notice_id:
          description: Unused message ID for the history notice (max 128 chars, differs from bundle_id, never reusable).
          type: string
          maxLength: 128
          example: 3EB0C767D26A1D6B8AA1
        recipients:
          description: "1-100 distinct other current members: phone, @s.whatsapp.net or @lid JID without device."
          type: array
          items:
            type: string
          example:
            - "5511999999999"
            - 123456789012345@lid
    ChatFavoriteRequest:
      type: object
      description: Adds one chat to, or removes it from, the Favorites list.
      required:
        - phone
        - favorite
      properties:
        phone:
          type: string
          description: Chat to change. Phone number with country code (no `+`), user JID (`@s.whatsapp.net` or `@lid`) or group JID (`@g.us`).
          example: "5511999999999"
        favorite:
          type: boolean
          description: "`true` adds the chat to Favorites, `false` removes it."
          example: true
        force:
          type: boolean
          default: false
          description: Only with `favorite=true`. When the current list is unknown, replace it with only this chat instead of failing with 409.
          example: false
    ChatFavoriteResponse:
      description: Success envelope of `POST /chat/favorite`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the Favorites update.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the update.
                  example: true
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                index_jid:
                  type: string
                  description: JID written to the sync action (the LID when the mapping is known).
                  example: 123456789012345@lid
                favorite:
                  type: boolean
                  description: Echo of the requested `favorite` value.
                  example: true
                favorites:
                  type: array
                  description: Complete ordered Favorites list after the change.
                  items:
                    type: string
                  example:
                    - 120363000000000011@g.us
                    - 123456789012345@lid
    ChatFavoritesListResponse:
      description: Success envelope of `GET /chat/favorites`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Favorites list as known by the gateway.
              properties:
                favorites:
                  type: array
                  description: Ordered chat JIDs in Favorites. Empty while `known` is false.
                  items:
                    type: string
                  example:
                    - 123456789012345@lid
                    - 120363000000000011@g.us
                known:
                  type: boolean
                  description: "`true` once the list was observed from app state or sent through this API."
                  example: true
                complete:
                  type: boolean
                  description: "`true` only after a successful full sync (`refresh=true`)."
                  example: true
                source:
                  type: string
                  description: Where the list came from. Empty while `known` is false.
                  enum:
                    - app_state
                    - api
                    - ""
                  example: app_state
                synced_at:
                  type: string
                  format: date-time
                  description: UTC time of the last successful full sync. Omitted when no full sync has succeeded.
                  example: "2026-09-15T01:40:00Z"
    ChatFavoritesReplaceRequest:
      type: object
      description: Complete ordered Favorites list to write.
      required:
        - phones
      properties:
        phones:
          type: array
          description: Complete ordered list of chats (phone numbers, user JIDs or group JIDs). An empty array clears Favorites.
          items:
            type: string
            description: Phone number with country code, user JID or group JID.
            example: "5511999999999"
          example:
            - "5511999999999"
            - "120363000000000011@g.us"
    ChatFavoritesReplaceResponse:
      description: Success envelope of `PUT /chat/favorites`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the Favorites replacement.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the update.
                  example: true
                favorites:
                  type: array
                  description: Ordered list exactly as written to the sync action (LIDs when known).
                  items:
                    type: string
                  example:
                    - 123456789012345@lid
                    - 120363000000000011@g.us
    SessionS3ConfigRequest:
      type: object
      description: Complete S3 storage configuration of the instance. The call is a full replace; omitted fields are stored empty, false or 0.
      properties:
        enabled:
          type: boolean
          description: Enables media uploads to this bucket. Omitted is stored as `false`.
          example: true
        endpoint:
          type: string
          description: S3 API endpoint URL. Leave empty for AWS S3; required for MinIO, B2, R2 and other providers.
          example: https://minio.example.com
        region:
          type: string
          description: Bucket region.
          example: us-east-1
        bucket:
          type: string
          description: Bucket that receives the media objects.
          example: whatsapp-media
        access_key:
          type: string
          description: Access key ID. Stored as sent and never returned by `GET /session/s3/config`.
          example: minio-access-example
        secret_key:
          type: string
          description: Secret access key. Stored as sent and never returned.
          example: minio-secret-example
        path_style:
          type: boolean
          description: Use path-style addressing (`endpoint/bucket/key`). Required by MinIO and most self-hosted providers.
          example: true
        public_url:
          type: string
          description: Optional base URL (for example a CDN) used to build the media links placed in events.
          example: https://cdn.example.com
        media_delivery:
          type: string
          description: "How media appears in events: `base64` inline, `s3` link (`url` is an alias stored as `s3`), or `both`. Empty or omitted is stored as `base64`."
          enum:
            - base64
            - s3
            - url
            - both
          example: both
        retention_days:
          type: integer
          description: Days to keep uploaded objects. `0` means no expiration.
          minimum: 0
          example: 30
        disable_acl:
          type: boolean
          description: Skip object ACLs on upload. Set to `true` for AWS buckets with "Bucket owner enforced" object ownership.
          example: false
    SessionS3ConfigView:
      type: object
      description: Stored S3 configuration as returned by `GET /session/s3/config`. The secret key is omitted.
      properties:
        enabled:
          type: boolean
          description: Whether media uploads to S3 are enabled.
          example: true
        endpoint:
          type: string
          description: S3 API endpoint URL; empty for AWS S3.
          example: https://minio.example.com
        region:
          type: string
          description: Bucket region.
          example: us-east-1
        bucket:
          type: string
          description: Bucket that receives the media objects.
          example: whatsapp-media
        access_key:
          type: string
          description: Always `***` (masked), even when no key is stored.
          example: "***"
        path_style:
          type: boolean
          description: Whether path-style addressing is used.
          example: true
        public_url:
          type: string
          description: Base URL used to build media links; empty when not set.
          example: https://cdn.example.com
        media_delivery:
          type: string
          description: Stored delivery mode. `url` is stored and returned as `s3`.
          enum:
            - base64
            - s3
            - both
          example: s3
        retention_days:
          type: integer
          description: Days to keep uploaded objects; `0` means no expiration.
          example: 30
        disable_acl:
          type: boolean
          description: Whether object ACLs are skipped on upload.
          example: false
    SessionS3ConfigSaveResponse:
      description: Success envelope of `POST /session/s3/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Save confirmation.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: S3 configuration saved successfully
                Enabled:
                  type: boolean
                  description: Echo of the saved `enabled` value.
                  example: true
    SessionS3ConfigGetResponse:
      description: Success envelope of `GET /session/s3/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/SessionS3ConfigView"
    SessionS3ConfigDeleteResponse:
      description: Success envelope of `DELETE /session/s3/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Delete confirmation.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: S3 configuration deleted successfully
    SessionS3TestResponse:
      description: Success envelope of `POST /session/s3/test`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the bucket access test.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: S3 connection test successful
                Bucket:
                  type: string
                  description: Bucket that was listed.
                  example: whatsapp-media
                Region:
                  type: string
                  description: Region of the tested configuration.
                  example: us-east-1
    SessionRabbitMQConfigRequest:
      type: object
      description: Complete RabbitMQ publisher configuration of the instance. The call is a full replace; omitted fields are stored as false, empty or NULL.
      properties:
        enabled:
          type: boolean
          description: Enables event publishing to RabbitMQ. Omitted is stored as `false`.
          example: true
        url:
          type: string
          description: AMQP connection URL (`amqp://` or `amqps://`, with credentials and optional vhost).
          example: amqps://zzg_user:zzg_pass@rabbitmq.example.com:5671/whatsapp
        exchange:
          type: string
          description: Exchange that receives the events. Defaults to `zuckzapgo.user.<userID>` when empty and `enabled` is true.
          example: whatsapp.events
        exchange_type:
          type: string
          description: Exchange type. Defaults to `topic` when empty and `enabled` is true; validated only when enabled.
          enum:
            - topic
            - direct
            - fanout
            - headers
          example: topic
        queue:
          type: string
          description: "Queue name. Supports `{user_id}` and `{event_type}` placeholders; with `{event_type}` one queue per event type is declared. Defaults to `zuckzapgouser.<userID>.events` when empty and enabled."
          example: user_{user_id}_event_{event_type}
        queue_type:
          type: string
          description: Queue type. Defaults to `classic` when empty and `enabled` is true; validated only when enabled.
          enum:
            - classic
            - quorum
            - stream
          example: quorum
        routing_key:
          type: string
          description: "Routing key. Supports `{user_id}` and `{event_type}` placeholders. Defaults to `events.#` when empty and enabled."
          example: whatsapp.{user_id}.{event_type}
        events:
          type: string
          description: "Comma-separated canonical event names (case-sensitive) or `All`, for example `Message,ReadReceipt,Connected`. Spaces around names are trimmed. Empty means no events."
          example: Message,ReadReceipt,Connected
        durable:
          type: boolean
          description: Declare the exchange and queues as durable.
          example: true
        auto_delete:
          type: boolean
          description: Declare the exchange and queues with auto-delete.
          example: false
        exclusive:
          type: boolean
          description: Declare queues as exclusive to the publishing connection.
          example: false
        no_wait:
          type: boolean
          description: Declare exchange and queues without waiting for broker confirmation.
          example: false
        delivery_mode:
          type: integer
          description: AMQP delivery mode, `1` transient or `2` persistent. `0` or omitted is stored as `2`.
          enum:
            - 1
            - 2
          example: 2
        dead_letter_exchange:
          type: string
          description: Dead-letter exchange for rejected or expired messages (`x-dead-letter-exchange`).
          example: dlx.zuckzapgo
        dead_letter_routing_key:
          type: string
          description: Routing key used when dead-lettering (`x-dead-letter-routing-key`).
          example: dlq.events
        message_ttl:
          type: integer
          format: int64
          description: Message TTL in milliseconds (`x-message-ttl`).
          example: 86400000
        max_length:
          type: integer
          format: int64
          description: Maximum number of messages per queue (`x-max-length`).
          example: 100000
        max_length_bytes:
          type: integer
          format: int64
          description: Maximum queue size in bytes (`x-max-length-bytes`).
          example: 104857600
        queue_arguments:
          type: string
          description: Extra queue arguments as a JSON-encoded string. A JSON object here fails with `400 could not decode payload`.
          example: '{"x-overflow":"reject-publish"}'
        exchange_arguments:
          type: string
          description: Extra exchange arguments as a JSON-encoded string. A JSON object here fails with `400 could not decode payload`.
          example: "{}"
        connection_pool_size:
          type: integer
          description: Publisher connection pool size. Must be greater than 0; omitted uses the server default.
          minimum: 1
          example: 50
        worker_count:
          type: integer
          description: Concurrent publisher workers. Must be greater than 0; omitted uses the server default.
          minimum: 1
          example: 50
        queue_buffer_size:
          type: integer
          description: In-memory event buffer size before publishing. Must be greater than 0; omitted uses the server default.
          minimum: 1
          example: 100000
        batch_size:
          type: integer
          description: Events published per batch. Must be greater than 0; omitted uses the server default.
          minimum: 1
          example: 500
        batch_timeout_ms:
          type: integer
          description: Maximum wait in milliseconds before publishing a partial batch. Must be non-negative.
          minimum: 0
          example: 100
        publish_timeout_ms:
          type: integer
          description: Timeout in milliseconds for a single publish. Must be greater than 0.
          minimum: 1
          example: 5000
        max_retries:
          type: integer
          description: Retry attempts for a failed publish. Must be non-negative.
          minimum: 0
          example: 3
        retry_delay_ms:
          type: integer
          description: Delay in milliseconds between publish retries. Must be non-negative.
          minimum: 0
          example: 1000
    SessionRabbitMQConfigView:
      type: object
      description: Stored RabbitMQ configuration as returned by `GET /session/rabbitmq/config`. Optional fields appear only when a value is stored.
      properties:
        enabled:
          type: boolean
          description: Whether event publishing is enabled.
          example: true
        url:
          type: string
          description: Connection URL with the password replaced by `***`.
          example: "amqps://zzg_user:***@rabbitmq.example.com:5671/whatsapp"
        exchange:
          type: string
          description: Exchange name.
          example: whatsapp.events
        exchange_type:
          type: string
          description: Exchange type; empty when never enabled.
          example: topic
        queue:
          type: string
          description: Queue name or pattern.
          example: user_{user_id}_event_{event_type}
        queue_type:
          type: string
          description: Queue type; empty when never enabled.
          example: quorum
        routing_key:
          type: string
          description: Routing key or pattern.
          example: whatsapp.{user_id}.{event_type}
        events:
          type: string
          description: Comma-separated subscribed events.
          example: Message,ReadReceipt,Connected
        durable:
          type: boolean
          description: Durable declaration flag.
          example: true
        auto_delete:
          type: boolean
          description: Auto-delete declaration flag.
          example: false
        exclusive:
          type: boolean
          description: Exclusive queue flag.
          example: false
        no_wait:
          type: boolean
          description: No-wait declaration flag.
          example: false
        delivery_mode:
          type: integer
          description: AMQP delivery mode (`1` transient, `2` persistent).
          example: 2
        connection_pool_size:
          type: integer
          description: Stored connection pool size. Present only when set.
          example: 50
        worker_count:
          type: integer
          description: Stored worker count. Present only when set.
          example: 50
        queue_buffer_size:
          type: integer
          description: Stored buffer size. Present only when set.
          example: 100000
        batch_size:
          type: integer
          description: Stored batch size. Present only when set.
          example: 500
        batch_timeout_ms:
          type: integer
          description: Stored batch timeout in milliseconds. Present only when set.
          example: 100
        publish_timeout_ms:
          type: integer
          description: Stored publish timeout in milliseconds. Present only when set.
          example: 5000
        max_retries:
          type: integer
          description: Stored retry attempts. Present only when set.
          example: 3
        retry_delay_ms:
          type: integer
          description: Stored retry delay in milliseconds. Present only when set.
          example: 1000
        dead_letter_exchange:
          type: string
          description: Dead-letter exchange. Present only when non-empty.
          example: dlx.zuckzapgo
        dead_letter_routing_key:
          type: string
          description: Dead-letter routing key. Present only when non-empty.
          example: dlq.events
        message_ttl:
          type: integer
          format: int64
          description: Message TTL in milliseconds. Present only when greater than 0.
          example: 86400000
        max_length:
          type: integer
          format: int64
          description: Maximum queue length. Present only when greater than 0.
          example: 100000
        max_length_bytes:
          type: integer
          format: int64
          description: Maximum queue size in bytes. Present only when greater than 0.
          example: 104857600
        queue_arguments:
          type: string
          description: JSON-encoded extra queue arguments. Present only when non-empty.
          example: '{"x-overflow":"reject-publish"}'
        exchange_arguments:
          type: string
          description: JSON-encoded extra exchange arguments. Present only when non-empty.
          example: "{}"
    SessionRabbitMQQueueStats:
      type: object
      nullable: true
      description: In-memory queue statistics of this process for the instance. Reset on restart; `null` when the RabbitMQ manager is not running.
      properties:
        userID:
          type: string
          description: Instance (user) ID.
          example: 4e4942c7dee1deef99ab8fd9f7350de5
        dynamicQueues:
          type: boolean
          description: Whether the instance uses per-event dynamic queues.
          example: true
        createdQueues:
          type: integer
          description: Number of queues declared by this process.
          example: 2
        queueNames:
          type: array
          description: Names of the queues declared by this process.
          items:
            type: string
          example:
            - user_4e4942c7dee1deef99ab8fd9f7350de5_event_Message
            - user_4e4942c7dee1deef99ab8fd9f7350de5_event_ReadReceipt
    SessionRabbitMQConfigSaveResponse:
      description: Success envelope of `POST /session/rabbitmq/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Save confirmation.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: RabbitMQ configuration saved successfully
                Enabled:
                  type: boolean
                  description: Echo of the saved `enabled` value.
                  example: true
    SessionRabbitMQConfigGetResponse:
      description: Success envelope of `GET /session/rabbitmq/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored configuration and queue statistics.
              properties:
                config:
                  $ref: "#/components/schemas/SessionRabbitMQConfigView"
                stats:
                  $ref: "#/components/schemas/SessionRabbitMQQueueStats"
    SessionRabbitMQConfigDeleteResponse:
      description: Success envelope of `DELETE /session/rabbitmq/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Delete confirmation.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: RabbitMQ configuration deleted successfully
    SessionRabbitMQTestResponse:
      description: Success envelope of `POST /session/rabbitmq/test`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Effective configuration used by the test.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: RabbitMQ connection test successful
                Exchange:
                  type: string
                  description: Exchange declared and used for the test message.
                  example: whatsapp.events
                ExchangeType:
                  type: string
                  description: Exchange type used.
                  example: topic
                Queue:
                  type: string
                  description: Queue name or pattern used.
                  example: user_{user_id}_event_{event_type}
                QueueType:
                  type: string
                  description: Queue type used.
                  example: quorum
                RoutingKey:
                  type: string
                  description: Routing key or pattern used.
                  example: whatsapp.{user_id}.{event_type}
                TestMessage:
                  type: string
                  description: Confirmation that the test event was published.
                  example: Test message published successfully
                URL:
                  type: string
                  description: Always `***`; the broker URL is never returned.
                  example: "***"
    SessionSkipMediaUpdateResponse:
      description: Success envelope of `POST /session/skipmedia/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored media skip flag.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip media download configuration updated successfully
                SkipMediaDownload:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionSkipMediaGetResponse:
      description: Success envelope of `GET /session/skipmedia/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Per-instance and server-wide media skip settings.
              properties:
                UserSkipMediaDownload:
                  type: boolean
                  description: Per-instance value stored with `POST /session/skipmedia/config`.
                  example: true
                GlobalSkipMediaDownload:
                  type: boolean
                  description: Value of the `GLOBAL_SKIP_MEDIA_DOWNLOAD` environment variable (read-only).
                  example: false
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip media download configuration retrieved successfully
    SessionSkipGroupsUpdateResponse:
      description: Success envelope of `POST /session/skipgroups/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored group skip flag.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip groups configuration updated successfully
                SkipGroups:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionSkipGroupsGetResponse:
      description: Success envelope of `GET /session/skipgroups/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Per-instance group skip flag.
              properties:
                SkipGroups:
                  type: boolean
                  description: Whether group events are dropped for this instance.
                  example: true
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip groups configuration retrieved successfully
    SessionSkipNewslettersUpdateResponse:
      description: Success envelope of `POST /session/skipnewsletters/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored newsletter skip flag.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip newsletters configuration updated successfully
                SkipNewsletters:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionSkipNewslettersGetResponse:
      description: Success envelope of `GET /session/skipnewsletters/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Per-instance newsletter skip flag.
              properties:
                SkipNewsletters:
                  type: boolean
                  description: Whether newsletter events are dropped for this instance.
                  example: false
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip newsletters configuration retrieved successfully
    SessionSkipBroadcastsUpdateResponse:
      description: Success envelope of `POST /session/skipbroadcasts/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored broadcast skip flag.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip broadcasts configuration updated successfully
                SkipBroadcasts:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionSkipBroadcastsGetResponse:
      description: Success envelope of `GET /session/skipbroadcasts/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Per-instance broadcast skip flag.
              properties:
                SkipBroadcasts:
                  type: boolean
                  description: Whether broadcast and Status events are dropped for this instance.
                  example: true
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip broadcasts configuration retrieved successfully
    SessionSkipOwnMessagesUpdateResponse:
      description: Success envelope of `POST /session/skipownmessages/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored own-message skip flag.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip own messages configuration updated successfully
                SkipOwnMessages:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionSkipOwnMessagesGetResponse:
      description: Success envelope of `GET /session/skipownmessages/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Per-instance own-message skip flag.
              properties:
                SkipOwnMessages:
                  type: boolean
                  description: Whether events about this account's own messages are dropped.
                  example: false
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip own messages configuration retrieved successfully
    SessionSkipCallsUpdateResponse:
      description: Success envelope of `POST /session/skipcalls/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored auto-reject settings, including applied defaults.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip calls configuration updated successfully
                SkipCalls:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
                RejectMessage:
                  type: string
                  description: Stored message sent to callers (default applied when empty).
                  example: Sorry, I cannot take calls right now. Please send a message.
                RejectType:
                  type: string
                  description: Stored rejection label (default `busy` applied when empty).
                  example: busy
    SessionSkipCallsGetResponse:
      description: Success envelope of `GET /session/skipcalls/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored auto-reject settings.
              properties:
                SkipCalls:
                  type: boolean
                  description: Whether incoming calls are rejected automatically.
                  example: true
                RejectMessage:
                  type: string
                  description: Message sent to callers after rejection.
                  example: Sorry, I cannot take calls right now. Please send a message.
                RejectType:
                  type: string
                  description: Stored rejection label.
                  example: busy
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Skip calls configuration retrieved successfully
    EchoAPISettingsUpdateResponse:
      description: Success envelope of `POST /session/echo/api`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored echo flag.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Echo API messages configuration updated successfully
                EchoAPIMessages:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionPresenceConfigRequest:
      type: object
      description: Automatic "unavailable" presence setting.
      properties:
        enabled:
          type: boolean
          description: "`true` sends `unavailable` 3 seconds after the post-connection `available` presence; `false` stays available. Omitted is treated as `false`."
          example: true
    SessionPresenceConfigUpdateResponse:
      description: Success envelope of `POST /session/presence/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored presence setting.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Auto presence unavailable configuration updated successfully
                AutoPresenceUnavailable:
                  type: boolean
                  description: Stored per-instance value.
                  example: true
    SessionPresenceConfigGetResponse:
      description: Success envelope of `GET /session/presence/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored presence setting.
              properties:
                AutoPresenceUnavailable:
                  type: boolean
                  description: Whether `unavailable` is sent 3 seconds after connecting.
                  example: true
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Auto presence unavailable configuration retrieved successfully
    WebhookGetResponse:
      description: Success envelope of `GET /webhook`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored webhook configuration.
              properties:
                webhook:
                  type: string
                  description: Individual webhook URL; empty when not configured.
                  example: https://example.com/webhooks/whatsapp
                subscribe:
                  type: array
                  description: Subscribed event types. `[""]` when nothing is stored.
                  items:
                    type: string
                  example:
                    - Message
                    - ReadReceipt
                    - Connected
    WebhookSetResponse:
      description: Success envelope of `POST /webhook`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored webhook URL.
              properties:
                webhook:
                  type: string
                  description: URL stored for the instance; empty when cleared.
                  example: https://example.com/webhooks/whatsapp
    WebhookUpdateResponse:
      description: Success envelope of `PUT /webhook`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the update.
              properties:
                webhook:
                  type: string
                  description: Stored URL; empty when `active` is false.
                  example: https://example.com/webhooks/whatsapp
                events:
                  type: array
                  nullable: true
                  description: Accepted event types from the request; `null` when none was sent or accepted.
                  items:
                    type: string
                  example:
                    - Message
                    - ReadReceipt
                active:
                  type: boolean
                  description: Echo of the requested `active` value.
                  example: true
    WebhookDeleteResponse:
      description: Success envelope of `DELETE /webhook`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Delete confirmation.
              properties:
                Details:
                  type: string
                  description: Human-readable confirmation.
                  example: Webhook and events deleted successfully
    HealthStatusResponse:
      type: object
      description: Body of `GET /health` for both 200 and 503. Health fields are written at the top level of the gateway envelope.
      required:
        - code
        - data
        - success
        - status
        - version
        - uptime
        - timestamp
        - metrics
        - environment
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body (200 or 503).
          example: 200
        data:
          type: string
          description: Legacy health message, `OK` for 200 and `ERROR` for 503.
          enum:
            - OK
            - ERROR
          example: OK
        success:
          type: boolean
          description: "`true` for 200, `false` for 503."
          example: true
        status:
          type: string
          description: Aggregated status derived from the dependency checks.
          enum:
            - healthy
            - degraded
            - unhealthy
          example: healthy
        version:
          type: string
          description: Running release version.
          example: v1.20.4
        uptime:
          type: integer
          format: int64
          description: Seconds since the process started.
          example: 86400
        timestamp:
          type: string
          format: date-time
          description: UTC time the snapshot was generated (snapshots are cached for 3 seconds).
          example: "2026-09-15T12:00:00Z"
        metrics:
          type: object
          description: Go runtime metrics.
          properties:
            goroutines:
              type: integer
              description: Number of running goroutines.
              example: 42
            go_version:
              type: string
              description: Go runtime version.
              example: go1.25.1
            cpu_count:
              type: integer
              description: Logical CPUs visible to the process.
              example: 8
            go_max_procs:
              type: integer
              description: Current GOMAXPROCS value.
              example: 8
        environment:
          type: object
          description: Deployment metadata.
          properties:
            containerized:
              type: boolean
              description: Whether the process runs inside a container.
              example: true
            hostname:
              type: string
              description: Host name; omitted when unavailable.
              example: zuckzapgo-01
            app_env:
              type: string
              description: Value of `APP_ENV` or `ENVIRONMENT`; omitted when unset.
              example: production
            app_region:
              type: string
              description: Value of `APP_REGION`; omitted when unset.
              example: us-east-1
        capabilities:
          type: object
          description: Additive API capabilities supported by this gateway build. A client reads this to know which optional contracts the gateway honours, without probing routes.
          required: [ webhook_fallback ]
          properties:
            webhook_fallback:
              type: integer
              enum: [ 1 ]
              description: Version of the per-user webhook fallback contract.
              example: 1
        build:
          type: object
          description: Build information; present only when `GIT_COMMIT` is set.
          properties:
            commit:
              type: string
              description: Deployed git commit.
              example: a1b2c3d4
        services:
          type: object
          description: Dependency checks keyed by service name. Currently only `database`.
          properties:
            database:
              type: object
              description: Database ping result.
              properties:
                status:
                  type: string
                  description: Database health.
                  enum:
                    - up
                    - degraded
                    - down
                  example: up
                latency_ms:
                  type: number
                  description: Ping latency in milliseconds; omitted when no database is configured.
                  example: 1.42
                open_connections:
                  type: integer
                  description: Open connections in the pool.
                  example: 10
                in_use:
                  type: integer
                  description: Connections currently in use.
                  example: 2
                idle:
                  type: integer
                  description: Idle connections.
                  example: 8
                driver:
                  type: string
                  description: Database driver or configured type.
                  example: postgres
                error:
                  type: string
                  description: Ping error; present only when `status` is `down`.
                  example: context deadline exceeded
    NewsletterLegacyErrorEnvelope:
      type: object
      description: "Error response of newsletter handlers that mix two shapes: the standard error envelope (`error` set, `success: false`) and a legacy envelope where the failure is inside `data` and the top-level `success` stays `true`."
      required:
        - code
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 500
        error:
          type: string
          description: Error message. Present only in the standard error envelope.
          example: no session
        data:
          type: object
          description: Legacy failure payload. Present only in the legacy envelope.
          properties:
            success:
              type: boolean
              description: Always `false` in the legacy failure payload.
              example: false
            message:
              type: string
              description: Failure message produced by the gateway.
              example: "Error listing newsletters: websocket not connected"
        success:
          type: boolean
          description: "`false` in the standard error envelope; `true` in the legacy envelope (check `data.success`)."
          example: false
    NewsletterText:
      type: object
      description: Versioned text field of the raw channel metadata.
      properties:
        text:
          type: string
          description: Current text value.
          example: Product Updates
        id:
          type: string
          description: Version ID of this text.
          example: "1757900000123456"
        update_time:
          type: string
          description: Last update time in Unix microseconds, as a string.
          example: "1757900000123456"
    NewsletterPictureInfo:
      type: object
      description: Channel picture handle returned by WhatsApp.
      properties:
        url:
          type: string
          description: Download URL of the picture.
          example: https://mmg.whatsapp.net/v/t61.24694-24/example-channel-picture.jpg
        id:
          type: string
          description: Picture ID.
          example: "1757900001"
        type:
          type: string
          description: "`image` for the full picture, `preview` for the thumbnail."
          example: image
        direct_path:
          type: string
          description: CDN direct path of the picture.
          example: /v/t61.24694-24/example-channel-picture.jpg
        hash:
          type: string
          format: byte
          nullable: true
          description: Picture hash as base64, or `null` when WhatsApp does not send it.
          example: null
    NewsletterRawMetadata:
      type: object
      description: One channel as returned by WhatsApp (`types.NewsletterMetadata`).
      properties:
        id:
          type: string
          description: Channel JID.
          example: 120363000000000022@newsletter
        state:
          type: object
          description: Channel state.
          properties:
            type:
              type: string
              description: "`active`, `suspended` or `geosuspended`."
              example: active
        thread_metadata:
          type: object
          description: Channel profile data.
          properties:
            creation_time:
              type: string
              description: Creation time in Unix seconds, as a string (`"0"` when unknown).
              example: "1757900000"
            invite:
              type: string
              description: Invite code used in `https://whatsapp.com/channel/` links.
              example: 0029VaExampleInviteCode01
            name:
              $ref: "#/components/schemas/NewsletterText"
            description:
              $ref: "#/components/schemas/NewsletterText"
            subscribers_count:
              type: string
              description: Number of followers, as a string.
              example: "1500"
            verification:
              type: string
              description: "`verified` or `unverified`."
              example: unverified
            picture:
              $ref: "#/components/schemas/NewsletterPictureInfo"
            preview:
              $ref: "#/components/schemas/NewsletterPictureInfo"
            settings:
              type: object
              description: Channel settings.
              properties:
                reaction_codes:
                  type: object
                  description: Which reactions followers may send.
                  properties:
                    value:
                      type: string
                      description: "`all`, `basic`, `none` or `blocklist`."
                      example: all
        viewer_metadata:
          type: object
          nullable: true
          description: This account's relation to the channel; may be `null`.
          properties:
            mute:
              type: string
              description: "`on` when notifications are muted, `off` otherwise."
              example: "off"
            role:
              type: string
              description: "`owner`, `admin`, `subscriber` or `guest`."
              example: owner
    NewsletterRawListResponse:
      description: Success envelope of `GET /newsletter/list`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Followed channels.
              properties:
                Newsletter:
                  type: array
                  description: Raw metadata of each followed channel; empty when the account follows no channel.
                  items:
                    $ref: "#/components/schemas/NewsletterRawMetadata"
    NewsletterCreateRequest:
      type: object
      description: New channel data.
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          description: Channel name. Cannot be blank; cut to its first 25 bytes.
          example: Product Updates
        description:
          type: string
          description: Optional channel description.
          example: Release notes and product news
        picture:
          type: string
          description: Optional channel picture as a base64 data URL or HTTP(S) URL (JPEG, PNG, GIF or WebP), converted to a JPEG of at most 640x640.
          example: https://example.com/images/channel-cover.png
    NewsletterCreateResponse:
      description: Success envelope of `POST /newsletter/create`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Created channel.
              properties:
                success:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                jid:
                  type: string
                  description: JID of the new channel.
                  example: 120363000000000022@newsletter
                name:
                  type: string
                  description: Name actually used (after the 25-byte cut).
                  example: Product Updates
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Newsletter created successfully
    NewsletterSubscribedItem:
      type: object
      description: One followed channel in simplified form.
      properties:
        jid:
          type: string
          description: Channel JID.
          example: 120363000000000022@newsletter
        name:
          type: string
          description: Channel name.
          example: Product Updates
        description:
          type: string
          description: Channel description; empty string when not set.
          example: Release notes and product news
        subscribers:
          type: integer
          description: Number of followers.
          example: 1500
        muted:
          type: boolean
          description: "`true` when this account muted the channel notifications."
          example: false
        invite_code:
          type: string
          description: Invite code; empty string when WhatsApp does not return it.
          example: 0029VaExampleInviteCode01
    NewsletterSubscribedResponse:
      description: Success envelope of `GET /newsletter/subscribed`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Followed channels.
              properties:
                success:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                newsletters:
                  type: array
                  description: Followed channels. Omitted when the account follows no channel.
                  items:
                    $ref: "#/components/schemas/NewsletterSubscribedItem"
    NewsletterInfoResponse:
      description: Success envelope of `POST /newsletter/info`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Channel metadata.
              properties:
                jid:
                  type: string
                  description: Channel JID.
                  example: 120363000000000022@newsletter
                name:
                  type: string
                  description: Channel name.
                  example: Product Updates
                description:
                  type: string
                  description: Channel description. Omitted when empty.
                  example: Release notes and product news
                subscribers:
                  type: integer
                  description: Number of followers.
                  example: 1500
                invite_code:
                  type: string
                  description: Invite code. Omitted when empty.
                  example: 0029VaExampleInviteCode01
                muted:
                  type: boolean
                  description: "`true` when this account muted the channel; `false` when not muted or when viewer data is unavailable."
                  example: false
                role:
                  type: string
                  description: This account's role. Omitted when WhatsApp returns no viewer data.
                  enum:
                    - owner
                    - admin
                    - subscriber
                    - guest
                  example: owner
    NewsletterDeleteResponse:
      description: Success envelope of `DELETE /newsletter/{newsletterID}`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Deletion result.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp confirmed the deletion.
                  example: true
                newsletter_jid:
                  type: string
                  description: JID of the deleted channel.
                  example: 120363000000000022@newsletter
    NewsletterActionResponse:
      description: Success envelope of newsletter actions that only confirm the operation (follow, unfollow, mute, reaction, mark viewed, accept terms).
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation of the action.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the action.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text of the operation.
                  example: Subscribed successfully
    NewsletterFollowInviteResponse:
      description: Success envelope of `POST /newsletter/follow-invite`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Follow result with the resolved channel.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the channel was followed.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Subscribed successfully
                jid:
                  type: string
                  description: JID of the channel resolved from the invite code.
                  example: 120363000000000022@newsletter
    NewsletterMuteRequest:
      type: object
      description: Channel and mute state to apply.
      required:
        - jid
      properties:
        jid:
          type: string
          description: Channel JID (`<digits>@newsletter`). Aliases `NewsletterJID`, `newsletter_jid`, `NewsletterID`, `newsletterID`.
          example: 120363000000000022@newsletter
        mute:
          type: boolean
          description: "`true` mutes the channel notifications, `false` unmutes them. Alias `Mute`. Must be a JSON boolean; omitted or `null` unmutes."
          example: true
    NewsletterSendRequest:
      type: object
      description: Channel post to publish. Send `jid` plus one content type.
      required:
        - jid
      properties:
        jid:
          type: string
          description: Channel JID (`<digits>@newsletter`).
          example: 120363000000000022@newsletter
        message:
          type: string
          description: Text of a text post. Takes precedence over every other content type.
          example: Version 2.4 is live. Check the release notes on our website.
        image:
          type: string
          description: Image as a base64 data URL or HTTP(S) URL.
          example: https://example.com/images/release-2-4.jpg
        video:
          type: string
          description: Video as a base64 data URL or HTTP(S) URL; the MP4 duration is detected when possible.
          example: https://example.com/videos/release-2-4.mp4
        document:
          type: string
          description: Document as a base64 data URL or HTTP(S) URL.
          example: https://example.com/files/release-notes-2-4.pdf
        audio:
          type: string
          description: Audio as a base64 data URL or HTTP(S) URL; always sent as a voice note (PTT).
          example: https://example.com/audio/weekly-briefing.ogg
        sticker:
          type: string
          description: Sticker (WebP) as a base64 data URL or HTTP(S) URL.
          example: https://example.com/stickers/launch.webp
        caption:
          type: string
          description: Caption for `image`, `video` or `document`. Ignored for other types.
          example: Version 2.4 highlights
        filename:
          type: string
          description: Document file name and title. Defaults to `document`.
          example: release-notes-2-4.pdf
        question:
          type: string
          description: Poll question. Sent as a poll only with at least 2 `options`.
          example: Which feature should ship next?
        options:
          type: array
          description: Poll options; at least 2.
          items:
            type: string
            description: Option text.
            example: Dark mode
          example:
            - Dark mode
            - Offline sync
        max_answer:
          type: integer
          minimum: 1
          default: 1
          description: How many options a follower may select. Values below 1 become 1; above the number of options returns 400.
          example: 1
    NewsletterSendResponse:
      description: Success envelope of `POST /newsletter/send`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Published post.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the post was sent.
                  example: true
                id:
                  type: string
                  description: Message ID of the post (not the numeric server ID).
                  example: 3EB0C767D26A1D6B8A9F
                timestamp:
                  type: integer
                  format: int64
                  description: Server timestamp of the post in Unix seconds.
                  example: 1757901234
    NewsletterPostMedia:
      type: object
      description: Media details of a channel post. Present only for image, video, document, audio and sticker posts.
      properties:
        type:
          type: string
          description: Media kind.
          enum:
            - image
            - video
            - document
            - audio
            - sticker
          example: image
        mimetype:
          type: string
          description: MIME type of the media.
          example: image/jpeg
        url:
          type: string
          description: Media download URL.
          example: https://mmg.whatsapp.net/v/t62.7118-24/example-post-image.jpg
        sha256:
          type: string
          description: SHA-256 of the file as base64.
          example: n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
        size:
          type: integer
          format: int64
          description: File size in bytes.
          example: 48213
        title:
          type: string
          description: Document title. Documents only.
          example: release-notes-2-4.pdf
        duration:
          type: integer
          description: Audio duration in seconds. Audio only.
          example: 34
    NewsletterPost:
      type: object
      description: One channel post.
      properties:
        id:
          type: string
          description: Message ID of the post.
          example: 3EB0C767D26A1D6B8A9F
        server_id:
          type: integer
          description: Numeric server ID used by `POST /newsletter/reaction` and `POST /newsletter/mark-viewed`.
          example: 121
        type:
          type: string
          description: Stanza type attribute sent by WhatsApp, such as `text`, `media` or `poll`. Use `media.type` for the media kind.
          example: text
        timestamp:
          type: integer
          format: int64
          description: Post time in Unix seconds.
          example: 1757901234
        text:
          type: string
          description: Text of a text post. Omitted when empty.
          example: Version 2.4 is live
        caption:
          type: string
          description: Caption of an image or video post. Omitted when empty.
          example: Version 2.4 highlights
        views_count:
          type: integer
          description: Number of views.
          example: 42
        reaction_counts:
          type: object
          description: Reaction totals keyed by emoji. Omitted when there are no reactions.
          additionalProperties:
            type: integer
          example:
            "👍": 3
        media:
          $ref: "#/components/schemas/NewsletterPostMedia"
    NewsletterMessagesResponse:
      description: Success envelope of `POST /newsletter/messages`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Page of channel posts.
              properties:
                success:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                messages:
                  type: array
                  description: Channel posts; empty when the channel has no posts in the requested range.
                  items:
                    $ref: "#/components/schemas/NewsletterPost"
    NewsletterReactionRequest:
      type: object
      description: Reaction to set on a channel post. Unknown keys are ignored.
      required:
        - jid
        - server_id
      properties:
        jid:
          type: string
          description: Channel JID (`<digits>@newsletter`). Aliases `NewsletterJID`, `newsletter_jid`.
          example: 120363000000000022@newsletter
        server_id:
          type: integer
          minimum: 1
          description: Positive numeric server ID of the post (number or numeric string). Alias `message_id`.
          example: 121
        reaction:
          type: string
          description: Reaction emoji; an empty string, `null` or omitting it removes the reaction. Alias `emoji`.
          example: "👍"
    NewsletterMarkViewedRequest:
      type: object
      description: Posts to mark as viewed. Unknown keys are ignored.
      required:
        - jid
        - server_ids
      properties:
        jid:
          type: string
          description: Channel JID (`<digits>@newsletter`). Aliases `NewsletterJID`, `newsletter_jid`.
          example: 120363000000000022@newsletter
        server_ids:
          type: array
          minItems: 1
          description: At least one positive server ID (numbers or numeric strings). Alias `message_ids`.
          items:
            type: integer
            minimum: 1
            description: Server ID of a post.
            example: 120
          example:
            - 120
            - 121
    NewsletterLiveUpdatesRequest:
      type: object
      description: Channel whose live updates should be pushed to this session.
      required:
        - jid
      properties:
        jid:
          type: string
          description: Channel JID (`<digits>@newsletter`). Aliases `NewsletterJID`, `newsletter_jid`, `NewsletterID`, `newsletterID`.
          example: 120363000000000022@newsletter
    NewsletterLiveUpdatesResponse:
      description: Success envelope of `POST /newsletter/subscribe-live-updates`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Live updates subscription.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the subscription.
                  example: true
                duration:
                  type: number
                  description: Subscription length in seconds as stated by WhatsApp; `0` when not stated.
                  example: 300
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Subscribed to live updates
    NewsletterMessageUpdatesRequest:
      type: object
      description: Channel and cursor of the updates query. Unknown keys are ignored.
      required:
        - jid
      properties:
        jid:
          type: string
          description: Channel JID (`<digits>@newsletter`). Aliases `NewsletterJID`, `newsletter_jid`.
          example: 120363000000000022@newsletter
        count:
          type: integer
          default: 25
          description: Number of posts to cover; `0`, negative or omitted means 25 and other values are passed to WhatsApp unchanged.
          example: 50
        since:
          type: integer
          format: int64
          minimum: 0
          description: Only changes after this Unix time (seconds).
          example: 1757890000
        after:
          type: integer
          minimum: 0
          description: Updates for posts newer than this server ID. Wins over `before` when both are sent.
          example: 120
        before:
          type: integer
          minimum: 0
          description: Updates for posts older than this server ID (exclusive). Ignored when `after` is also sent.
          example: 121
    NewsletterPostUpdateMedia:
      type: object
      description: Reduced media details of a post update (image, video and document posts only).
      properties:
        type:
          type: string
          description: Media kind.
          enum:
            - image
            - video
            - document
          example: image
        mimetype:
          type: string
          description: MIME type of the media.
          example: image/jpeg
        url:
          type: string
          description: Media download URL.
          example: https://mmg.whatsapp.net/v/t62.7118-24/example-post-image.jpg
        title:
          type: string
          description: Document title. Documents only.
          example: release-notes-2-4.pdf
    NewsletterPostUpdate:
      type: object
      description: Current counters of one channel post.
      properties:
        id:
          type: string
          description: Message ID of the post.
          example: 3EB0C767D26A1D6B8A9F
        server_id:
          type: integer
          description: Numeric server ID of the post.
          example: 121
        type:
          type: string
          description: Stanza type attribute sent by WhatsApp, such as `text` or `media`.
          example: text
        timestamp:
          type: integer
          format: int64
          description: Post time in Unix seconds.
          example: 1757901234
        views_count:
          type: integer
          description: Current number of views.
          example: 58
        reaction_counts:
          type: object
          description: Reaction totals keyed by emoji. Omitted when there are no reactions.
          additionalProperties:
            type: integer
          example:
            "👍": 5
        text:
          type: string
          description: Post text when WhatsApp includes the message. Omitted when empty.
          example: Version 2.4 is live
        caption:
          type: string
          description: Image or video caption when included. Omitted when empty.
          example: Version 2.4 highlights
        media:
          $ref: "#/components/schemas/NewsletterPostUpdateMedia"
    NewsletterMessageUpdatesResponse:
      description: Success envelope of `POST /newsletter/message-updates`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Post updates.
              properties:
                success:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                updates:
                  type: array
                  description: Updates of the selected posts; empty when nothing matches.
                  items:
                    $ref: "#/components/schemas/NewsletterPostUpdate"
    NewsletterAcceptTOSRequest:
      type: object
      description: Terms of service notice to accept. Send `{}` to accept the channel creation notice.
      properties:
        notice_id:
          type: string
          default: "20601218"
          description: Notice ID; `20601218` (channel creation notice) when omitted or empty.
          example: "20601218"
        stage:
          type: string
          default: "5"
          description: Notice stage; `5` when omitted or empty.
          example: "5"
    NewsletterAdminInviteRequest:
      type: object
      description: Admin invite to create and send.
      required:
        - phone
        - newsletter_jid
        - image
        - caption
        - name
      properties:
        phone:
          type: string
          description: Invitee as a phone number with country code, `@s.whatsapp.net` JID or `@lid` JID.
          example: "5511999999999"
        newsletter_jid:
          type: string
          description: JID of the channel owned by this account (`<digits>@newsletter`).
          example: 120363000000000022@newsletter
        image:
          type: string
          description: Image for the invite thumbnail, as a base64 data URL or HTTP(S) URL.
          example: https://example.com/images/channel-cover.png
        caption:
          type: string
          description: Invite message text.
          example: Join me as admin of Product Updates
        name:
          type: string
          description: Channel name shown in the invite card.
          example: Product Updates
    NewsletterAdminInviteResponse:
      description: Success envelope of `POST /newsletter/admin-invite`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Created invite and sent message.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the invite message was sent.
                  example: true
                id:
                  type: string
                  description: Message ID of the invite message.
                  example: 3EB0A1B2C3D4E5F60718
                timestamp:
                  type: integer
                  format: int64
                  description: Server timestamp of the invite message (Unix seconds).
                  example: 1757901234
                recipient:
                  type: string
                  description: Echo of the `phone` field as sent.
                  example: "5511999999999"
                newsletter_jid:
                  type: string
                  description: Echo of `newsletter_jid` as sent.
                  example: 120363000000000022@newsletter
                invite_name:
                  type: string
                  description: Echo of `name`.
                  example: Product Updates
                expires_at:
                  type: integer
                  format: int64
                  description: Invite expiration returned by WhatsApp (Unix seconds).
                  example: 1758506034
                invitee_lid:
                  type: string
                  description: LID the invite was created for and sent to.
                  example: 123456789012345@lid
                invite_created:
                  type: boolean
                  description: Always `true`; the server-side invite exists.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Newsletter admin invite sent successfully
    GroupDetailsResponse:
      description: Success envelope of group actions that only return a confirmation text.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation payload.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation text of the operation.
                  example: Group Name set successfully
    GroupHistorySendResult:
      type: object
      description: Server acknowledgement state of a history send. Acknowledgements do not prove that recipients received the history.
      properties:
        bundle_id:
          type: string
          description: Message ID used for the `MessageHistoryBundle` message.
          example: 3EB0C767D26A1D6B8AA0
        notice_id:
          type: string
          description: Message ID used for the `MessageHistoryNotice` message.
          example: 3EB0C767D26A1D6B8AA1
        phase:
          type: string
          description: Last step reached. `acknowledged` on success; otherwise the step that failed.
          enum:
            - validation
            - upload
            - bundle
            - notice
            - acknowledged
          example: acknowledged
        bundle_acknowledged:
          type: boolean
          description: "`true` when the server acknowledged the bundle message."
          example: true
        notice_acknowledged:
          type: boolean
          description: "`true` when the server acknowledged the notice message."
          example: true
    GroupHistorySendResponse:
      description: "Success envelope of `POST /group/history/send` (200). The 502 body uses `GroupHistorySendFailureResponse`."
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Outcome of the history send.
              properties:
                success:
                  type: boolean
                  description: "`true` only when both messages were acknowledged. `false` on 502."
                  example: true
                error:
                  type: string
                  description: Present only on 502. Fixed hint that the IDs must not be retried.
                  example: history operation failed or remains unconfirmed; do not retry reserved IDs
                result:
                  $ref: "#/components/schemas/GroupHistorySendResult"
                retry_safe:
                  type: boolean
                  description: Always `false`. The reserved IDs can never be reused.
                  example: false
                recipient_delivery_verified:
                  type: boolean
                  description: Always `false`. Server acknowledgements do not prove recipient delivery.
                  example: false
                evidence_persisted:
                  type: boolean
                  description: "`true` when the terminal state of the reservation was stored."
                  example: true
    GroupPhotoSetResponse:
      description: Success envelope of `POST /group/photo`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation and the new picture ID.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation text.
                  example: Group Photo set successfully
                PictureID:
                  type: string
                  description: Picture ID assigned by WhatsApp to the new photo.
                  example: "1757862000"
    GroupParticipantInfo:
      type: object
      description: One participant of a group, as returned by WhatsApp.
      properties:
        JID:
          type: string
          description: Primary JID to address this participant; equals `LID` or `PhoneNumber`.
          example: 123456789012345@lid
        PhoneNumber:
          type: string
          description: Phone JID when WhatsApp shares it; empty otherwise.
          example: 5511999999999@s.whatsapp.net
        LID:
          type: string
          description: LID of the participant; empty when unknown.
          example: 123456789012345@lid
        Username:
          type: string
          description: WhatsApp username, when the participant has one.
          example: ""
        IsAdmin:
          type: boolean
          description: "`true` for admins (including the super admin)."
          example: false
        IsSuperAdmin:
          type: boolean
          description: "`true` for the group creator/super admin."
          example: false
        DisplayName:
          type: string
          description: Obfuscated name shown for anonymous members of announcement groups; usually empty.
          example: ""
        Error:
          type: integer
          description: Non-zero when WhatsApp could not add this participant during group creation (for example `403` when an invite is required).
          example: 0
        AddRequest:
          type: object
          nullable: true
          description: Invite that can be sent to a participant WhatsApp refused to add directly; `null` otherwise.
          properties:
            Code:
              type: string
              description: Invite code for this participant.
              example: AbCdEfGhIjKlMnOp
            Expiration:
              type: string
              format: date-time
              description: When the invite code expires.
              example: "2026-09-18T12:00:00Z"
    GroupInfoData:
      type: object
      description: Group information as returned by the WhatsApp library (`types.GroupInfo`). Keys are PascalCase; unset JIDs are empty strings and unset times are `0001-01-01T00:00:00Z`.
      properties:
        JID:
          type: string
          description: Group JID.
          example: "120363000000000011@g.us"
        OwnerJID:
          type: string
          description: JID of the group creator (often a LID).
          example: 123456789012340@lid
        OwnerPN:
          type: string
          description: Phone JID of the creator when WhatsApp shares it.
          example: 5511977777777@s.whatsapp.net
        Name:
          type: string
          description: Group subject.
          example: Project Team
        NameSetAt:
          type: string
          format: date-time
          description: When the subject was last changed.
          example: "2026-09-15T12:00:00Z"
        NameSetBy:
          type: string
          description: Who last changed the subject.
          example: 123456789012340@lid
        NameSetByPN:
          type: string
          description: Phone JID of who last changed the subject, when shared.
          example: 5511977777777@s.whatsapp.net
        Topic:
          type: string
          description: Group description.
          example: Weekly status and deadlines
        TopicID:
          type: string
          description: Version ID of the description.
          example: "1757937600000000"
        TopicSetAt:
          type: string
          format: date-time
          description: When the description was last changed.
          example: "2026-09-15T12:05:00Z"
        TopicSetBy:
          type: string
          description: Who last changed the description.
          example: 123456789012340@lid
        TopicSetByPN:
          type: string
          description: Phone JID of who last changed the description, when shared.
          example: 5511977777777@s.whatsapp.net
        TopicDeleted:
          type: boolean
          description: "`true` when the description was removed."
          example: false
        IsLocked:
          type: boolean
          description: "`true` when only admins can edit group info (`POST /group/locked`)."
          example: true
        IsAnnounce:
          type: boolean
          description: "`true` when only admins can send messages (`POST /group/announce`)."
          example: false
        AnnounceVersionID:
          type: string
          description: Version ID of the announce setting.
          example: "1757937600000000"
        IsEphemeral:
          type: boolean
          description: "`true` when disappearing messages are on."
          example: true
        DisappearingTimer:
          type: integer
          description: Disappearing messages timer in seconds (`86400`, `604800`, `7776000`); `0` when off.
          example: 604800
        IsIncognito:
          type: boolean
          description: Incognito flag reported by WhatsApp.
          example: false
        IsParent:
          type: boolean
          description: "`true` when this group is a community (parent group)."
          example: false
        DefaultMembershipApprovalMode:
          type: string
          description: Community-only default join approval mode (`request_required`); empty for regular groups.
          example: ""
        LinkedParentJID:
          type: string
          description: JID of the parent community when the group is linked to one; empty otherwise.
          example: "120363000000000033@g.us"
        IsDefaultSubGroup:
          type: boolean
          description: "`true` for the community announcement group."
          example: false
        IsJoinApprovalRequired:
          type: boolean
          description: "`true` when new members need admin approval (`POST /group/joinapproval`)."
          example: true
        AddressingMode:
          type: string
          description: How participants are addressed. `lid` groups identify people by LID.
          enum:
            - pn
            - lid
          example: lid
        GroupCreated:
          type: string
          format: date-time
          description: Creation time of the group.
          example: "2026-09-15T12:00:00Z"
        CreatorCountryCode:
          type: string
          description: Country code of the creator's phone number.
          example: BR
        ParticipantVersionID:
          type: string
          description: Version ID of the participant list.
          example: "1757937600000001"
        Participants:
          type: array
          nullable: true
          description: Participants with admin flags. `null` when there are none to report, including every group of `GET /group/list?ignoreParticipants=true`.
          items:
            $ref: "#/components/schemas/GroupParticipantInfo"
        ParticipantCount:
          type: integer
          description: Number of participants reported by WhatsApp.
          example: 12
        MemberAddMode:
          type: string
          description: Who can add members (`POST /group/memberaddmode`). Empty when WhatsApp does not report it.
          enum:
            - admin_add
            - all_member_add
            - ""
          example: admin_add
        Suspended:
          type: boolean
          description: "`true` when WhatsApp suspended the group."
          example: false
    GroupInfoResponse:
      description: Success envelope whose `data` is one group's information.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GroupInfoData"
    GroupListResponse:
      description: Success envelope of `GET /group/list`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Joined groups.
              properties:
                Groups:
                  type: array
                  nullable: true
                  description: Every joined group; `null` when the session is in no group.
                  items:
                    $ref: "#/components/schemas/GroupInfoData"
    GroupInviteLinkResponse:
      description: Success envelope of `GET /group/invitelink`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Invite link payload.
              properties:
                InviteLink:
                  type: string
                  description: Full invite link of the group.
                  example: "https://chat.whatsapp.com/AbCdEfGhIjKlMnOpQrStUv"
    CommunityLinkRequest:
      type: object
      description: Community and the group to link to it.
      required:
        - parent_group
        - child_group
      properties:
        parent_group:
          type: string
          description: Community (parent group) JID.
          example: "120363000000000033@g.us"
        child_group:
          type: string
          description: JID of the group to link as a sub-group.
          example: "120363000000000011@g.us"
    CommunityLinkResponse:
      description: Success envelope of `POST /community/link`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Link confirmation.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Group linked to community successfully
                parent_group:
                  type: string
                  description: Normalized community JID.
                  example: "120363000000000033@g.us"
                child_group:
                  type: string
                  description: Normalized sub-group JID.
                  example: "120363000000000011@g.us"
    CommunityUnlinkRequest:
      type: object
      description: Group to remove from its community.
      required:
        - group
      properties:
        group:
          type: string
          description: JID of the linked sub-group. Its parent community is looked up automatically.
          example: "120363000000000011@g.us"
    CommunityUnlinkResponse:
      description: Success envelope of `POST /community/unlink`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Unlink confirmation.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Group unlinked from community successfully
                group:
                  type: string
                  description: Normalized JID of the unlinked group.
                  example: "120363000000000011@g.us"
    CommunityRequestsUpdateRequest:
      type: object
      description: Pending requests to approve or reject.
      required:
        - group
        - action
        - requests
      properties:
        group:
          type: string
          description: Group or community JID.
          example: "120363000000000011@g.us"
        action:
          type: string
          description: "Case-insensitive. `approve`/`accept` approve the requests; `reject`/`deny` reject them."
          enum:
            - approve
            - accept
            - reject
            - deny
          example: approve
        requests:
          type: array
          minItems: 1
          description: Requesters to update (LIDs, phone JIDs or phone numbers).
          items:
            type: string
            description: Requester JID or phone number.
            example: 123456789012345@lid
          example:
            - 123456789012345@lid
            - 5511988888888@s.whatsapp.net
        link_code:
          type: string
          description: Accepted for compatibility but ignored by the gateway.
          example: AbCdEfGhIjKlMnOp
    CommunityRequestsUpdateResponse:
      description: Success envelope of `POST /community/requests/update`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Update confirmation; per-request results are not included.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: "Built as `Successfully <action>ed <n> request(s)` from the raw `action` value."
                  example: Successfully rejected 1 request(s)
                group:
                  type: string
                  description: Normalized group JID.
                  example: "120363000000000011@g.us"
                action:
                  type: string
                  description: The `action` exactly as sent.
                  example: reject
                requests:
                  type: array
                  description: The `requests` array exactly as sent.
                  items:
                    type: string
                    description: Requester as sent.
                    example: 5511988888888@s.whatsapp.net
                  example:
                    - 5511988888888@s.whatsapp.net
    SessionConnectResponse:
      description: Success envelope of `POST /session/connect` and `POST /connector/connect`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored values of the instance after the subscription update.
              properties:
                details:
                  type: string
                  description: Fixed confirmation text.
                  example: Connected!
                events:
                  type: string
                  description: New comma-separated event subscription. Empty when no individual webhook event is subscribed.
                  example: Message,ReadReceipt,Connected
                jid:
                  type: string
                  description: Device JID stored before this call; empty before the first pairing.
                  example: 5511999999999:12@s.whatsapp.net
                skip_media_download:
                  type: boolean
                  description: Current value of the skip-media-download flag used by the new client.
                  example: false
                webhook:
                  type: string
                  description: Individual webhook URL stored for the instance; empty when none is set.
                  example: https://example.com/webhook
    SessionDisconnectResponse:
      description: Success envelope of `POST /session/disconnect` and `POST /connector/disconnect`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Client state before the disconnect.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation text.
                  example: Disconnected successfully
                wasConnected:
                  type: boolean
                  description: Whether the socket was connected before the call.
                  example: true
                wasLoggedIn:
                  type: boolean
                  description: Whether the device was paired (logged in) before the call.
                  example: true
    SessionLogoutResponse:
      description: Success envelope of `POST /session/logout`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the logout and the client state before it.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation text.
                  example: Logout completed
                logoutSuccess:
                  type: boolean
                  description: "`true` when WhatsApp confirmed the device removal, or when the client was not logged in. `false` when the logout request failed; the local session is removed anyway."
                  example: true
                wasConnected:
                  type: boolean
                  description: Whether the socket was connected before the call.
                  example: true
                wasLoggedIn:
                  type: boolean
                  description: Whether the device was paired before the call.
                  example: true
    SessionRefreshRequest:
      type: object
      description: Optional steps of `POST /session/refresh`. Every flag defaults to `true` when omitted.
      properties:
        sync_app_state:
          type: boolean
          default: true
          description: Fetch the five app-state collections from scratch after reconnecting.
          example: true
        refresh_prekeys:
          type: boolean
          default: true
          description: Read the server pre-key count and upload new keys when fewer than 5 remain.
          example: true
        refresh_privacy:
          type: boolean
          default: true
          description: Fetch the privacy settings from WhatsApp bypassing the cache.
          example: true
        auto_recovery:
          type: boolean
          default: true
          description: When an app-state collection fails, ask the primary phone for a recovery snapshot.
          example: true
    SessionRefreshData:
      type: object
      description: Result of a session refresh.
      properties:
        Details:
          type: string
          description: Fixed text, also present when the refresh failed.
          example: Connection refresh completed
        refreshSuccess:
          type: boolean
          description: "`true` when the new client is connected after the 3-second wait."
          example: true
        wasConnected:
          type: boolean
          description: Socket state before the refresh.
          example: true
        wasLoggedIn:
          type: boolean
          description: Login state before the refresh.
          example: true
        newConnected:
          type: boolean
          description: Socket state after the wait.
          example: true
        newLoggedIn:
          type: boolean
          description: Login state after the wait.
          example: true
        jid:
          type: string
          description: Stored device JID used to reconnect.
          example: 5511999999999:12@s.whatsapp.net
        error:
          type: string
          description: Present only when `refreshSuccess` is false.
          example: Failed to refresh connection
        sync_options:
          type: object
          description: Effective flags. Present only when the refresh succeeded and at least one step was enabled.
          properties:
            sync_app_state:
              type: boolean
              description: Effective `sync_app_state` flag.
              example: true
            refresh_prekeys:
              type: boolean
              description: Effective `refresh_prekeys` flag.
              example: true
            refresh_privacy:
              type: boolean
              description: Effective `refresh_privacy` flag.
              example: true
            auto_recovery:
              type: boolean
              description: Effective `auto_recovery` flag.
              example: true
        sync:
          type: object
          description: Results of the sync steps. Present under the same condition as `sync_options`; blocks of disabled steps are omitted.
          properties:
            app_state:
              type: array
              description: One entry per app-state collection, in fetch order.
              items:
                type: object
                description: Fetch result of one collection.
                properties:
                  name:
                    type: string
                    description: Collection name.
                    enum:
                      - critical_block
                      - critical_unblock_low
                      - regular_low
                      - regular_high
                      - regular
                    example: regular_high
                  success:
                    type: boolean
                    description: Whether the collection was fetched and applied.
                    example: false
                  error:
                    type: string
                    description: Fetch error text; omitted on success.
                    example: "failed to verify patch v42: mismatching LTHash"
                  duration:
                    type: string
                    description: Time spent on this collection (Go duration format).
                    example: 412.9ms
                  recovery_requested:
                    type: boolean
                    description: "`true` when a recovery request was sent to the primary phone; omitted otherwise."
                    example: true
            app_state_success:
              type: boolean
              description: "`true` when every collection succeeded; omitted when false."
              example: true
            prekeys:
              type: object
              description: Pre-key check result.
              properties:
                checked:
                  type: boolean
                  description: Whether the pre-key step ran.
                  example: true
                server_count:
                  type: integer
                  description: Pre-keys left on the server; omitted when zero or when the count failed.
                  example: 47
                upload_triggered:
                  type: boolean
                  description: "`true` when fewer than 5 keys remained and an upload was started; omitted otherwise."
                  example: false
                error:
                  type: string
                  description: Error text when the count could not be read.
                  example: "failed to get server prekey count: context deadline exceeded"
            privacy_settings:
              type: object
              description: Privacy settings fetch result.
              properties:
                fetched:
                  type: boolean
                  description: Whether the settings were fetched.
                  example: true
                error:
                  type: string
                  description: Error text when the fetch failed.
                  example: "failed to fetch privacy settings: info query timed out"
            total_duration:
              type: string
              description: Total time spent on the sync steps (Go duration format).
              example: 4.521s
    SessionRefreshResponse:
      description: Success envelope of `POST /session/refresh`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/SessionRefreshData"
    SessionRefreshFailureResponse:
      type: object
      description: "Body of a 500 from `POST /session/refresh`. A client that did not reconnect uses the legacy envelope (`success: true`, `data` filled, no top-level `error`); an encoding failure uses the error envelope (`success: false`, `error` filled)."
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 500
        data:
          $ref: "#/components/schemas/SessionRefreshData"
        error:
          type: string
          description: Error text; only on the encoding failure path.
          example: "json: unsupported value: NaN"
        success:
          type: boolean
          description: "`true` on the legacy not-reconnected body, `false` on the error envelope."
          example: true
    SessionStatusS3Config:
      type: object
      description: "S3 block of the session status. The secret key is never included. When the database read fails only `enabled: false` is returned."
      properties:
        enabled:
          type: boolean
          description: Whether per-instance S3 storage is enabled.
          example: true
        endpoint:
          type: string
          description: S3-compatible endpoint URL.
          example: https://s3.us-east-1.amazonaws.com
        region:
          type: string
          description: Bucket region.
          example: us-east-1
        bucket:
          type: string
          description: Bucket name.
          example: example-whatsapp-media
        access_key:
          type: string
          description: Access key ID as stored.
          example: AKIAIOSFODNN7EXAMPLE
        path_style:
          type: boolean
          description: Whether path-style URLs are used (MinIO and similar).
          example: false
        public_url:
          type: string
          description: Public base URL used to build media links.
          example: https://cdn.example.com
        media_delivery:
          type: string
          description: How media reaches webhooks (`base64`, `s3` or `both`).
          example: both
        retention_days:
          type: integer
          description: Object retention in days; 0 keeps objects forever.
          example: 30
        disable_acl:
          type: boolean
          description: Whether uploads skip the object ACL.
          example: true
    SessionStatusRabbitMQConfig:
      type: object
      description: "RabbitMQ block of the session status, as stored. When the database read fails only `enabled: false` is returned."
      properties:
        enabled:
          type: boolean
          description: Whether the per-instance RabbitMQ transport is enabled.
          example: true
        url:
          type: string
          description: AMQP URL as stored, credentials included.
          example: amqp://guest:guest@rabbitmq.example.com:5672/
        exchange:
          type: string
          description: Exchange name.
          example: whatsapp.events
        exchange_type:
          type: string
          description: Exchange type.
          example: topic
        queue:
          type: string
          description: Queue name.
          example: whatsapp.instance.events
        queue_type:
          type: string
          description: Queue type.
          example: classic
        routing_key:
          type: string
          description: Routing key template.
          example: whatsapp.{event_type}
        events:
          type: string
          description: Comma-separated event types published to RabbitMQ.
          example: Message,ReadReceipt
        durable:
          type: boolean
          description: Whether the queue is durable.
          example: true
        auto_delete:
          type: boolean
          description: Whether the queue is auto-deleted.
          example: false
        exclusive:
          type: boolean
          description: Whether the queue is exclusive.
          example: false
        no_wait:
          type: boolean
          description: Whether declarations use no-wait.
          example: false
        delivery_mode:
          type: integer
          description: AMQP delivery mode (1 transient, 2 persistent).
          example: 2
        dead_letter_exchange:
          type: string
          description: "`x-dead-letter-exchange` queue argument."
          example: dlx.whatsapp
        dead_letter_routing_key:
          type: string
          description: "`x-dead-letter-routing-key` queue argument."
          example: dlq.events
        message_ttl:
          type: integer
          description: "`x-message-ttl` in milliseconds; 0 when unset."
          example: 86400000
        max_length:
          type: integer
          description: "`x-max-length`; 0 when unset."
          example: 100000
        max_length_bytes:
          type: integer
          description: "`x-max-length-bytes`; 0 when unset."
          example: 104857600
        queue_arguments:
          type: string
          description: Extra queue arguments as a raw JSON string.
          example: '{"x-overflow":"reject-publish"}'
        exchange_arguments:
          type: string
          description: Extra exchange arguments as a raw JSON string.
          example: "{}"
    SessionStatusTransportSkips:
      type: object
      description: Per-instance flags that exclude this instance from each global transport.
      properties:
        skipGlobalWebhook:
          type: boolean
          description: Skip the global webhook.
          example: false
        skipGlobalRabbitMQ:
          type: boolean
          description: Skip the global RabbitMQ transport.
          example: false
        skipGlobalSQS:
          type: boolean
          description: Skip the global SQS transport.
          example: false
        skipGlobalRedis:
          type: boolean
          description: Skip the global Redis transport.
          example: false
        skipGlobalWebSocket:
          type: boolean
          description: Skip the global WebSocket transport.
          example: false
        skipGlobalS3:
          type: boolean
          description: Skip the global S3 upload.
          example: false
    SessionStatusResponse:
      description: Success envelope of `GET /session/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/SessionStatus"
    SessionPairPhoneResponse:
      description: Success envelope of `POST /session/pairphone` and `POST /connector/pairphone`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Linking code to type on the phone.
              properties:
                LinkingCode:
                  type: string
                  description: 8-character code formatted as `XXXX-XXXX`.
                  example: 9H3J-H3J8
    SessionQRResponse:
      description: Success envelope of `GET /session/qr` and `GET /connector/qr`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Current pairing QR code.
              properties:
                QRCode:
                  type: string
                  description: PNG image of the QR code as a `data:image/png;base64,` URL; empty until generated.
                  example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAA
                code:
                  type: string
                  description: Raw text encoded in the QR image; empty until generated.
                  example: 2@Xq3example0000QrTextValue,Zq9exampleNoiseKey=,Kp2exampleIdentityKey=,Tt5exampleAdvSecret=,1
    SessionProxyRequest:
      type: object
      description: Legacy proxy settings for `POST /session/proxy`.
      properties:
        enable:
          type: boolean
          default: false
          description: "`true` stores `proxy_url`; `false` or omitted removes the proxy."
          example: true
        proxy_url:
          type: string
          description: Proxy URL, required when `enable` is true. Schemes `http`, `https` or `socks5`.
          example: socks5://proxyuser:proxypass@proxy.example.com:1080
    SessionProxyResponse:
      description: Success envelope of `POST /session/proxy`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the proxy update.
              properties:
                Details:
                  type: string
                  description: "`Proxy configured successfully` or `Proxy disabled successfully`."
                  example: Proxy configured successfully
                ProxyURL:
                  type: string
                  description: Stored URL; only when enabling.
                  example: socks5://proxyuser:proxypass@proxy.example.com:1080
                RequiresReconnection:
                  type: boolean
                  description: "`true` only for a connected client, which this endpoint rejects, so `false` in practice."
                  example: false
                Message:
                  type: string
                  description: Reconnection hint; only when `RequiresReconnection` is true.
                  example: Please disconnect and reconnect to apply proxy settings
    SessionPasskeyAllowedCredential:
      type: object
      description: Credential accepted for the WebAuthn ceremony.
      properties:
        id:
          type: string
          description: Credential ID, base64url without padding.
          example: Y3JlZGVudGlhbC1pZC0wMDAx
        type:
          type: string
          description: Credential type.
          example: public-key
        transports:
          type: array
          description: Transports hinted by WhatsApp; may be null.
          items:
            type: string
            description: WebAuthn transport name.
            example: internal
          example:
            - internal
            - hybrid
    SessionPasskeyPublicKey:
      type: object
      nullable: true
      description: WebAuthn `PublicKeyCredentialRequestOptions` sent by WhatsApp. Pass it verbatim to `navigator.credentials.get({publicKey})`.
      properties:
        challenge:
          type: string
          description: Challenge bytes, base64url without padding.
          example: ZmljdGl0aW91cy13ZWJhdXRobi1jaGFsbGVuZ2U
        timeout:
          type: integer
          description: Ceremony timeout in milliseconds.
          example: 60000
        rpId:
          type: string
          description: Relying party ID; always sent by WhatsApp, never built by the client.
          example: whatsapp.com
        allowCredentials:
          type: array
          description: Credentials allowed for this challenge.
          items:
            $ref: "#/components/schemas/SessionPasskeyAllowedCredential"
        userVerification:
          type: string
          description: User verification requirement.
          example: preferred
        extensions:
          type: object
          nullable: true
          additionalProperties: true
          description: WebAuthn extensions requested by WhatsApp; usually null.
          example: null
    SessionPasskeyStatusData:
      type: object
      description: In-memory passkey pairing state of the instance.
      properties:
        required:
          type: boolean
          description: "`true` while a challenge is stored and not confirmed."
          example: true
        challenge:
          $ref: "#/components/schemas/SessionPasskeyPublicKey"
        responseSubmitted:
          type: boolean
          description: Whether a WebAuthn response was accepted by WhatsApp.
          example: false
        confirmed:
          type: boolean
          description: Whether pairing was confirmed. The state is cleared right after a confirm, so this is rarely observed as true.
          example: false
        confirmationCode:
          type: string
          description: Code to compare on the phone; empty until WhatsApp sends it.
          example: "482913"
        skipHandoffUX:
          type: boolean
          description: "`true` when no code comparison is needed and the gateway confirms automatically."
          example: false
        error:
          type: string
          description: Last passkey error text; empty when none.
          example: ""
    SessionPasskeyStatusResponse:
      description: Success envelope of `GET /session/passkey/status` and `GET /connector/passkey/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/SessionPasskeyStatusData"
    SessionPasskeyChallengeResponse:
      description: Success envelope of `POST /session/passkey/challenge` and `POST /connector/passkey/challenge`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Challenge stored for the instance.
              properties:
                required:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                challenge:
                  $ref: "#/components/schemas/SessionPasskeyPublicKey"
    SessionPasskeyUnavailableResponse:
      description: "503 body of the passkey challenge probe. Legacy envelope: `success` is `true` and the error code is inside `data`."
      allOf:
        - $ref: "#/components/schemas/LegacyErrorEnvelope"
        - type: object
          properties:
            code:
              type: integer
              description: HTTP status code repeated in the body.
              example: 503
            data:
              type: object
              description: Machine-readable reason.
              properties:
                error:
                  type: string
                  description: Error code.
                  enum:
                    - not_connected
                    - passkey_unavailable
                  example: passkey_unavailable
                reason:
                  type: string
                  description: Human-readable explanation or the upstream error text.
                  example: "info query returned status 404: item-not-found"
            success:
              type: boolean
              description: Always `true` on this legacy body, even though the status is 503.
              example: true
    SessionPasskeySubmitResponse:
      description: Success envelope of `POST /session/passkey/response` and `POST /connector/passkey/response`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Submission result.
              properties:
                status:
                  type: string
                  description: Always `submitted`.
                  example: submitted
                details:
                  type: string
                  description: Next-step hint.
                  example: Passkey response sent to WhatsApp. Await confirmation.
    SessionPasskeyConfirmResponse:
      description: Success envelope of `POST /session/passkey/confirm` and `POST /connector/passkey/confirm`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation result.
              properties:
                status:
                  type: string
                  description: Always `confirmed`.
                  example: confirmed
                details:
                  type: string
                  description: Next-step hint.
                  example: Passkey confirmation sent. Pairing should complete shortly.
    ConnectorTokenResponse:
      description: Success envelope of `POST /session/connector/token`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Ephemeral Connector token.
              properties:
                token:
                  type: string
                  description: Ephemeral token (`ct_` followed by 43 base64url characters). Send it in the `token` header or query of `/connector/*` calls.
                  example: ct_Qm9ndXNFeGFtcGxlVG9rZW5WYWx1ZTAwMDAwMDAwMDAwMA
                expiresIn:
                  type: integer
                  description: Lifetime in seconds (`CONNECTOR_TOKEN_TTL`, default 600).
                  example: 600
                tokenType:
                  type: string
                  description: Always `connector`.
                  example: connector
    ConnectorStatusData:
      type: object
      description: "Status snapshot returned to the Connector widget: `SessionStatus` without `token`, `webhook`, `events`, `proxy_url`, `proxy_config`, `s3_config` and `rabbitmq_config`."
      properties:
        id:
          type: string
          description: Instance (user) ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        name:
          type: string
          description: Instance name.
          example: Support Line
        avatar_url:
          type: string
          description: Profile picture URL from the 24-hour cache; empty when unknown.
          example: ""
        connected:
          type: boolean
          description: Whether the WhatsApp socket is connected.
          example: true
        loggedIn:
          type: boolean
          description: Whether the device is paired.
          example: false
        hasClient:
          type: boolean
          description: Whether a client exists in memory.
          example: true
        connectionHealth:
          type: string
          description: Summary derived from `hasClient`, `connected` and `loggedIn`.
          enum:
            - no_client
            - disconnected
            - connected_not_logged
            - connected
          example: connected_not_logged
        lastSuccessfulConnect:
          type: integer
          format: int64
          description: Unix time of the last successful connection; 0 without a client.
          example: 1757862000
        autoReconnectErrors:
          type: integer
          description: Consecutive failed automatic reconnect attempts.
          example: 0
        enableAutoReconnect:
          type: boolean
          description: Whether automatic reconnect is enabled on the client.
          example: true
        expiration:
          type: integer
          format: int64
          description: Seconds until the stored expiration timestamp.
          example: 86400
        jid:
          type: string
          description: Device JID; empty before pairing.
          example: ""
        qrcode:
          type: string
          description: Pending QR code as a PNG data URL; empty when none is pending.
          example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAA
        skip_media_download:
          type: boolean
          description: Media is not downloaded for events.
          example: false
        skip_groups:
          type: boolean
          description: Group events are skipped.
          example: false
        skip_newsletters:
          type: boolean
          description: Newsletter events are skipped.
          example: false
        skip_broadcasts:
          type: boolean
          description: Broadcast and status events are skipped.
          example: false
        skip_own_messages:
          type: boolean
          description: Messages sent by this account are skipped.
          example: false
        echo_api_messages:
          type: boolean
          description: Messages sent through the API are echoed to transports.
          example: false
        skip_calls:
          type: boolean
          description: Call events are skipped.
          example: false
        call_reject_message:
          type: string
          description: Text sent when a call is rejected automatically.
          example: ""
        call_reject_type:
          type: string
          description: Automatic call rejection mode as stored.
          example: ""
        globalTransportSkips:
          $ref: "#/components/schemas/SessionStatusTransportSkips"
        isFromAPI:
          type: boolean
          description: Same value as `echo_api_messages`.
          example: false
        auto_presence_unavailable:
          type: boolean
          description: Whether the client marks itself unavailable automatically after connecting.
          example: false
        passkeyRequired:
          type: boolean
          description: "`true` while a passkey challenge is stored and not confirmed."
          example: false
        passkeyResponseSubmitted:
          type: boolean
          description: Whether a WebAuthn response was already submitted.
          example: false
        passkeyConfirmed:
          type: boolean
          description: Whether the passkey pairing was confirmed.
          example: false
        passkeyConfirmationCode:
          type: string
          description: Confirmation code to compare on the phone; empty until received.
          example: ""
        passkeySkipHandoffUX:
          type: boolean
          description: "`true` when the gateway confirms automatically."
          example: false
        passkeyError:
          type: string
          description: Last passkey error text; empty when none.
          example: ""
        timestamp:
          type: integer
          format: int64
          description: Unix time when the snapshot was built.
          example: 1757865600
    ConnectorStatusResponse:
      description: Success envelope of `GET /connector/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/ConnectorStatusData"
    SessionImportResponse:
      description: Success envelope of `POST /session/import`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Import result.
              properties:
                status:
                  type: string
                  description: Always `imported`.
                  example: imported
                jid:
                  type: string
                  description: Device JID stored for the instance.
                  example: 5511999999999:12@s.whatsapp.net
                note:
                  type: string
                  description: Next-step hint.
                  example: device booting (BETA); poll /session/status for loggedIn
    SessionImportUnprocessableResponse:
      type: object
      description: "422 body of `POST /session/import`. Snapshot conversion errors use the error envelope (`error`, `success: false`); an incomplete generic dump uses the legacy envelope (`data`, `success: true`)."
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 422
        error:
          type: string
          description: Conversion error; only on the error envelope.
          example: "snapshot conversion failed: noiseKey: priv: expected 32 bytes, got 16"
        data:
          type: object
          description: Extraction report; only for a generic dump.
          properties:
            error:
              type: string
              description: Fixed explanation.
              example: "incomplete wa-web dump: auto-extraction could not resolve required fields"
            missing:
              type: array
              description: Required snapshot fields that could not be resolved.
              items:
                type: string
                description: Snapshot field name.
                example: meJid
              example:
                - meJid
                - registrationId
            hint:
              type: string
              description: How to build a structured snapshot instead.
              example: supply a structured WebDeviceSnapshot (meJid, registrationId, noiseKey, identityKey, signedPreKey, account) — see docs/session-import.md
        success:
          type: boolean
          description: "`false` on the error envelope, `true` on the legacy dump body."
          example: false
    LabelManageRequest:
      type: object
      description: Creates, edits or deletes one label.
      properties:
        label_id:
          type: string
          description: Numeric label ID. Omit to create a label with a generated ID; required when `delete` is `true`.
          example: "12"
        label_name:
          type: string
          description: Label name. Required unless `delete` is `true`.
          example: Follow-up
        label_color:
          type: integer
          format: int32
          description: Palette color index. Not range-checked; sent as `0` when omitted. Ignored on delete.
          example: 5
        delete:
          type: boolean
          default: false
          description: "`true` deletes the label identified by `label_id`."
          example: false
    LabelManageResponse:
      description: Success envelope of `POST /label/manage`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the label mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Label updated successfully` for create and edit, `Label deleted successfully` for delete."
                  example: Label updated successfully
                label_id:
                  type: string
                  description: Label ID used in the mutation (generated or given).
                  example: "13"
                label_id_generated:
                  type: boolean
                  description: "`true` when the gateway generated `label_id`."
                  example: true
                label_type:
                  type: string
                  description: List type sent with the mutation. Present for generated labels (`CUSTOM`) and for edits of labels observed from app state.
                  example: CUSTOM
                label_name:
                  type: string
                  description: Echo of `label_name`. Omitted on delete.
                  example: Hot leads
                label_color:
                  type: integer
                  description: Echo of `label_color`. Omitted on delete.
                  example: 3
    LabelChatRequest:
      type: object
      description: Applies or removes one label on one chat.
      required:
        - phone
        - label_id
      properties:
        phone:
          type: string
          description: Chat to label. Phone number with country code (no `+`), user JID (`@s.whatsapp.net` or `@lid`) or group JID (`@g.us`).
          example: "5511999999999"
        label_id:
          type: string
          description: ID of the label to apply or remove (see `GET /label/list`).
          example: "12"
        apply:
          type: boolean
          default: false
          description: "`true` applies the label, `false` removes it."
          example: true
    LabelChatResponse:
      description: Success envelope of `POST /label/chat`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the label association.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Label applied successfully` or `Label removed successfully`."
                  example: Label applied successfully
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                label_id:
                  type: string
                  description: Echo of `label_id`.
                  example: "12"
                applied:
                  type: boolean
                  description: Echo of `apply`.
                  example: true
                index_jid:
                  type: string
                  description: JID written to the mutation index (LID for people when known; groups keep their JID).
                  example: 123456789012345@lid
    LabelMessageRequest:
      type: object
      description: Applies or removes one label on one message.
      required:
        - phone
        - message_id
        - label_id
      properties:
        phone:
          type: string
          description: Chat that holds the message. Phone number, user JID or group JID.
          example: "5511999999999"
        message_id:
          type: string
          description: ID of the message to label.
          example: 3EB0C767D26A1D6B8A9F
        label_id:
          type: string
          description: ID of the label to apply or remove.
          example: "12"
        apply:
          type: boolean
          default: false
          description: "`true` applies the label, `false` removes it."
          example: true
    LabelMessageResponse:
      description: Success envelope of `POST /label/message`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the message label mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Label applied successfully` or `Label removed successfully`."
                  example: Label applied successfully
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                message_id:
                  type: string
                  description: Echo of `message_id`.
                  example: 3EB0C767D26A1D6B8A9F
                label_id:
                  type: string
                  description: Echo of `label_id`.
                  example: "12"
                applied:
                  type: boolean
                  description: Echo of `apply`.
                  example: true
    LabelListResponse:
      description: Success envelope of `GET /label/list`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Labels known to the gateway.
              properties:
                labels:
                  type: array
                  description: Labels sorted by order index (numeric ID when no order index is known).
                  items:
                    $ref: "#/components/schemas/LabelView"
                complete:
                  type: boolean
                  description: "`true` only after a successful full sync (`refresh=true`)."
                  example: true
                observed_from_app_state:
                  type: boolean
                  description: "`true` once at least one label was observed from app state."
                  example: true
                synced_at:
                  type: string
                  format: date-time
                  description: UTC time of the last successful full sync. Omitted when no full sync has succeeded.
                  example: "2026-09-15T01:39:35Z"
                note:
                  type: string
                  description: Fixed reminder that message labels are not displayed by the official apps.
                  example: message labels (label_message) are not shown by the official apps
    LabelView:
      type: object
      description: One label with the chats that carry it.
      properties:
        id:
          type: string
          description: Label ID.
          example: "16"
        name:
          type: string
          description: Label name.
          example: New customer
        color:
          type: integer
          description: Palette color index.
          example: 1
        deleted:
          type: boolean
          description: "`true` when the label was deleted."
          example: false
        predefined_id:
          type: integer
          description: Predefined Business label ID. Omitted when unknown.
          example: 1
        order_index:
          type: integer
          description: Position in the list. Omitted when unknown.
          example: 10
        is_active:
          type: boolean
          description: Whether the list is active. Omitted when unknown.
          example: true
        type:
          type: string
          description: Official list type (for example `UNREAD`, `FAVORITES`, `GROUPS`, `PREDEFINED`, `CUSTOM`). Omitted when unknown.
          example: PREDEFINED
        is_immutable:
          type: boolean
          description: Whether the list can be edited. Omitted when unknown.
          example: false
        source:
          type: string
          description: Where the label was last seen.
          enum:
            - app_state
            - api
          example: app_state
        updated_at:
          type: string
          format: date-time
          description: Time of the last change observed or sent.
          example: "2026-07-02T15:19:25-03:00"
        chats:
          type: array
          description: Chat JIDs that carry this label (LIDs for people when known).
          items:
            type: string
          example:
            - 123456789012345@lid
    ChatPinRequest:
      type: object
      description: Pins or unpins one chat in the chat list.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Chat to pin. Phone number with country code (no `+`), user JID (`@s.whatsapp.net` or `@lid`) or group JID (`@g.us`).
          example: "5511999999999"
        pin:
          type: boolean
          default: false
          description: "`true` pins the chat, `false` unpins it."
          example: true
    ChatPinResponse:
      description: Success envelope of `POST /chat/pin`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the pin mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Chat pinned successfully` or `Chat unpinned successfully`."
                  example: Chat pinned successfully
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                pinned:
                  type: boolean
                  description: Echo of `pin`.
                  example: true
    ChatArchiveRequest:
      type: object
      description: Archives or unarchives one chat.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Chat to archive. Phone number with country code (no `+`), user JID or group JID.
          example: "5511999999999"
        archive:
          type: boolean
          default: false
          description: "`true` archives the chat, `false` unarchives it."
          example: true
        last_message_timestamp:
          type: integer
          format: int64
          description: Unix time in seconds of the most recent message. Defaults to the current time.
          example: 1757862000
        last_message_id:
          type: string
          description: ID of the most recent message. When omitted no message key is sent.
          example: 3EB0C767D26A1D6B8A9F
        last_message_from_me:
          type: boolean
          default: false
          description: Whether the most recent message was sent by this account. Only used with `last_message_id`.
          example: false
        last_message_remote_jid:
          type: string
          description: Overrides the chat JID inside the message key. Only used with `last_message_id`.
          example: 5511999999999@s.whatsapp.net
    ChatArchiveResponse:
      description: Success envelope of `POST /chat/archive`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the archive mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Chat archived successfully` or `Chat unarchived successfully`."
                  example: Chat archived successfully
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                archived:
                  type: boolean
                  description: Echo of `archive`.
                  example: true
    ChatMuteRequest:
      type: object
      description: Mutes or unmutes one chat.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Chat to mute. Phone number with country code (no `+`), user JID or group JID.
          example: "5511999999999"
        mute:
          type: boolean
          default: false
          description: "`true` mutes the chat, `false` unmutes it."
          example: true
        mute_duration:
          type: string
          description: Mute length (case-insensitive). Omitted means `always`. Ignored when unmuting.
          enum:
            - 8h
            - 1w
            - always
          example: 8h
    ChatMuteResponse:
      description: Success envelope of `POST /chat/mute`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the mute mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Chat muted successfully` or `Chat unmuted successfully`."
                  example: Chat muted successfully
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                muted:
                  type: boolean
                  description: Echo of `mute`.
                  example: true
                duration:
                  type: string
                  description: Raw `mute_duration` as sent (empty when omitted). Only present when muting.
                  example: 8h
    ChatStarRequest:
      type: object
      description: Stars or unstars one message.
      required:
        - chat_jid
        - message_id
      properties:
        chat_jid:
          type: string
          description: Chat that holds the message. Phone number, user JID or group JID.
          example: 5511999999999@s.whatsapp.net
        sender_jid:
          type: string
          description: Author of the message. Needed for a message written by another participant in a group.
          example: 5511888888888@s.whatsapp.net
        message_id:
          type: string
          description: ID of the message to star or unstar.
          example: 3EB0C767D26A1D6B8A9F
        from_me:
          type: boolean
          default: false
          description: Whether the message was sent by this account. Must match the real author.
          example: true
        star:
          type: boolean
          default: false
          description: "`true` stars the message, `false` unstars it."
          example: true
    ChatStarResponse:
      description: Success envelope of `POST /chat/star`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the star mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: "`Message starred successfully` or `Message unstarred successfully`."
                  example: Message starred successfully
                chat_jid:
                  type: string
                  description: Chat JID parsed from `chat_jid`.
                  example: 5511999999999@s.whatsapp.net
                message_id:
                  type: string
                  description: Echo of `message_id`.
                  example: 3EB0C767D26A1D6B8A9F
                starred:
                  type: boolean
                  description: Echo of `star`.
                  example: true
                index_chat_jid:
                  type: string
                  description: Chat JID written to the mutation index (LID for people when known).
                  example: 123456789012345@lid
    ChatDeleteChatRequest:
      type: object
      description: Deletes one chat from the chat list.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Chat to delete. Phone number with country code (no `+`), user JID or group JID.
          example: "5511999999999"
        last_message_timestamp:
          type: integer
          format: int64
          description: Unix time in seconds of the most recent message. Required for the message key fields to be used.
          example: 1757862000
        last_message_id:
          type: string
          description: ID of the most recent message. Only used with `last_message_timestamp`.
          example: 3EB0C767D26A1D6B8A9F
        last_message_from_me:
          type: boolean
          default: false
          description: Whether the most recent message was sent by this account. Only used with `last_message_id`.
          example: false
        last_message_sender_jid:
          type: string
          description: Author of the most recent message. Only used for group chats with `last_message_id`.
          example: 5511888888888@s.whatsapp.net
        delete_media:
          type: boolean
          default: true
          description: Also delete the chat's media files. Defaults to `true`.
          example: true
        last_system_message_timestamp:
          type: integer
          format: int64
          description: Unix time in seconds of the last system notice to delete (for example disappearing-messages notices). Defaults to `last_message_timestamp`, or to the current time when neither is sent.
          example: 1757862000
    ChatDeleteChatResponse:
      description: Success envelope of `POST /chat/delete-chat`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the deleteChat mutation.
              properties:
                success:
                  type: boolean
                  description: Always `true` when WhatsApp accepted the mutation.
                  example: true
                message:
                  type: string
                  description: Always `Chat deleted successfully`.
                  example: Chat deleted successfully
                jid:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                delete_media:
                  type: boolean
                  description: Effective `delete_media` value (default applied).
                  example: true
    ChatPinMessageRequest:
      type: object
      description: Pins or unpins one message for every participant of the chat.
      required:
        - phone
        - message_id
        - from_me
        - pin
      properties:
        phone:
          type: string
          description: Chat that holds the message. Phone number, user JID or group JID.
          example: "5511999999999"
        message_id:
          type: string
          description: ID of the message to pin or unpin.
          example: 3EB0C767D26A1D6B8A9F
        from_me:
          type: boolean
          description: Whether the target message was sent by this account.
          example: true
        pin:
          type: boolean
          description: "`true` pins the message, `false` unpins it."
          example: true
        sender_jid:
          type: string
          description: Author of the message. Required in groups when `from_me` is `false`.
          example: 5511888888888@s.whatsapp.net
        duration_seconds:
          type: integer
          description: Pin duration in seconds. Only used when `pin` is `true`.
          enum:
            - 86400
            - 604800
            - 2592000
          default: 604800
          example: 604800
    ChatPinMessageResponse:
      description: Success envelope of `POST /chat/pin-message`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the pin message send.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the message was sent.
                  example: true
                id:
                  type: string
                  description: ID of the protocol message that carries the pin.
                  example: 3EB0B1C2D3E4F5A6B7C8
                chat:
                  type: string
                  description: Chat JID the pin was sent to (LID for people when known).
                  example: 123456789012345@lid
                message_id:
                  type: string
                  description: Echo of `message_id`.
                  example: 3EB0C767D26A1D6B8A9F
                pinned:
                  type: boolean
                  description: Echo of `pin`.
                  example: true
                timestamp:
                  type: integer
                  format: int64
                  description: Server timestamp of the send, in Unix seconds.
                  example: 1757862000
                duration_seconds:
                  type: integer
                  description: Effective pin duration. Only present when pinning.
                  example: 604800
    ChatDownloadMediaRequest:
      type: object
      description: Media reference copied from a `Message` webhook event (`imageMessage`, `videoMessage`, `audioMessage`, `documentMessage` or `stickerMessage`). Keys are matched case-insensitively, so the webhook object can be posted unchanged.
      required:
        - DirectPath
        - MediaKey
        - FileEncSHA256
        - FileSHA256
      properties:
        Url:
          type: string
          description: "`URL` of the media message. Accepted for compatibility but not used; the file is fetched through `DirectPath`."
          example: https://mmg.whatsapp.net/o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c
        DirectPath:
          type: string
          description: "`directPath` of the media message (starts with `/`). Without it the download fails with `no url present`."
          example: /o1/v/t62.7118-24/f2/m231/AQ_example_image.enc?ccb=9-4&oh=01_Q5Aa0000&oe=68F0A1B2&_nc_sid=e6ed6c
        MediaKey:
          type: string
          format: byte
          description: "`mediaKey` of the media message, standard base64 (not hex). Used to decrypt the file."
          example: aQGcesqhANsFce2gBbNeXnE4LU9wA6LM5lA1ztbBduM=
        Mimetype:
          type: string
          description: "`mimetype` of the media message. Not validated; echoed in the response and used as the data URL media type."
          example: image/jpeg
        FileEncSHA256:
          type: string
          format: byte
          description: "`fileEncSHA256` of the media message, standard base64: SHA-256 of the encrypted file."
          example: 8wdTCF7kBugCQhneOg2YLsDye4Eqwu7N8kzyXKJXoIU=
        FileSHA256:
          type: string
          format: byte
          description: "`fileSHA256` of the media message, standard base64: SHA-256 of the decrypted file."
          example: Gc6NIzcYSI596Ke0Y0sO1VstRgq8FpAUzOglP06aYyE=
        FileLength:
          type: integer
          format: int64
          description: "`fileLength` in bytes. Accepted but not used for the download."
          example: 48213
    ChatDownloadMediaResponse:
      description: Success envelope of the `/chat/download*` operations.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Decrypted media file.
              properties:
                Mimetype:
                  type: string
                  description: Echo of the request `Mimetype`.
                  example: image/jpeg
                Data:
                  type: string
                  description: Whole file as a data URL (`data:<Mimetype>;base64,<file>`).
                  example: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8U
    ChatDeleteMessageRequest:
      type: object
      description: Revokes (deletes for everyone) one message.
      required:
        - Phone
        - Id
      properties:
        Phone:
          type: string
          description: Chat that holds the message. Phone number with country code (no `+`), user JID or group JID.
          example: "5511999999999"
        Id:
          type: string
          description: ID of the message to revoke.
          example: 3EB0C767D26A1D6B8A9F
        Sender:
          type: string
          description: Original author of the message. Only for a group admin revoking another participant's message.
          example: 123456789012345@lid
    ChatDeleteMessageResponse:
      description: Success envelope of `POST /chat/delete`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the revoke.
              properties:
                Details:
                  type: string
                  description: Always `Deleted`.
                  example: Deleted
                Id:
                  type: string
                  description: ID of the revoked message (echo of `Id`).
                  example: 3EB0C767D26A1D6B8A9F
                Timestamp:
                  type: string
                  format: date-time
                  description: Server timestamp of the revoke.
                  example: "2026-09-15T10:30:00-03:00"
    ChatMarkReadRequest:
      type: object
      description: Read receipt for messages of one chat and one sender.
      required:
        - Id
        - Chat
      properties:
        Id:
          type: array
          description: Message IDs to mark as read (`event.Info.ID`). At least one; all from the same sender.
          items:
            type: string
          example:
            - 3EB0C767D26A1D6B8A9F
            - 3EB0A1B2C3D4E5F60718
        Chat:
          type: string
          description: Full chat JID as received (`event.Info.Chat`). Bare phone numbers are not converted.
          example: 5511999999999@s.whatsapp.net
        Sender:
          type: string
          description: Author JID (`event.Info.Sender`). Required in groups; ignored for direct chats.
          example: 123456789012345@lid
    ChatMarkReadResponse:
      description: Success envelope of `POST /chat/markread`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the receipt.
              properties:
                Details:
                  type: string
                  description: Always `Message(s) marked as read`.
                  example: Message(s) marked as read
    ChatReactRequest:
      type: object
      description: Adds or removes a reaction on one message.
      required:
        - Phone
        - Id
      properties:
        Phone:
          type: string
          description: Chat of the target message. Phone number for direct chats, `@g.us` JID for groups.
          example: "5511999999999"
        Body:
          type: string
          description: Reaction emoji. Empty string or `remove` removes the reaction.
          example: "👍"
        Id:
          type: string
          description: ID of the target message. Prefix with `me:` when the message was sent by this account.
          example: 2AFA4C0C20D815D8055A
        Sender:
          type: string
          description: Full JID of the author of the target message. Only used for another participant's message in a group.
          example: 123456789012345@lid
        Chat:
          type: string
          description: Exact chat JID from the webhook (`event.Info.Chat`). When set, used as both destination and key chat instead of `Phone`.
          example: 123456789012345@lid
    ChatReactResponse:
      description: Success envelope of `POST /chat/react`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the reaction send.
              properties:
                Details:
                  type: string
                  description: Always `Sent`.
                  example: Sent
                Id:
                  type: string
                  description: ID of the new reaction message (not the target message).
                  example: 3EB0D1E2F3A4B5C6D7E8
                Timestamp:
                  type: string
                  format: date-time
                  description: Server timestamp of the send.
                  example: "2026-09-15T10:30:00-03:00"
    ChatPresenceStateRequest:
      type: object
      description: Chat state (typing indicator) to send to one chat.
      required:
        - Phone
        - State
      properties:
        Phone:
          type: string
          description: Chat to notify. Phone number, user JID or group JID.
          example: "5511999999999"
        State:
          type: string
          description: "`composing` shows the indicator, `paused` clears it. Not validated by the gateway."
          enum:
            - composing
            - paused
          example: composing
        Media:
          type: string
          description: "`audio` shows recording audio instead of typing. Only used with `composing`."
          enum:
            - ""
            - audio
          example: audio
    ChatPresenceStateResponse:
      description: Success envelope of `POST /chat/presence`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the chat state send.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation text (spelling as returned by the gateway).
                  example: Chat presence set successfuly
    ChatRetryMessageRequest:
      type: object
      description: Resend request for a message this device could not process. Keys are case-insensitive.
      required:
        - MessageID
        - ChatJID
      properties:
        MessageID:
          type: string
          description: ID of the message to recover (`event.Info.ID`). Alias `messageId`.
          example: 3EB0C767D26A1D6B8A9F
        ChatJID:
          type: string
          description: Chat of the message, phone number or JID. Alias `chatJid`.
          example: 120363000000000011@g.us
        SenderJID:
          type: string
          description: Author of the message (participant in groups). Required unless `ForceRetry` is `true`. Alias `senderJid`.
          example: 5511888888888@s.whatsapp.net
        RetryType:
          type: string
          description: Retry mode; `incoming` and `auto` behave the same. Other values return 500.
          enum:
            - incoming
            - auto
          default: incoming
          example: incoming
        ForceRetry:
          type: boolean
          default: false
          description: Send the resend request even when `SenderJID` is empty.
          example: false
    ChatRetryMessageResponse:
      description: Success envelope of `POST /chat/retry/message`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the resend request.
              properties:
                Details:
                  type: string
                  description: Always `Message retry operation completed` on 200.
                  example: Message retry operation completed
                Found:
                  type: boolean
                  description: Always `true` on 200; means the resend request was sent, not that the message was found.
                  example: true
                Sources:
                  type: array
                  description: Mechanisms used; always `["retry_request"]` on 200.
                  items:
                    type: string
                  example:
                    - retry_request
                RetryAttempts:
                  type: integer
                  description: Number of resend requests sent (always 1 on 200).
                  example: 1
                RetryType:
                  type: string
                  description: Effective `RetryType` (default applied).
                  example: incoming
                CacheStatus:
                  type: string
                  description: Always `retry_initiated` on 200.
                  example: retry_initiated
                ProcessedAt:
                  type: string
                  format: date-time
                  description: Server time when the request was processed.
                  example: "2026-09-15T10:30:00-03:00"
                MessageData:
                  type: object
                  description: Details of the resend request.
                  properties:
                    retryMethod:
                      type: string
                      description: Always `unavailable_message_request`.
                      example: unavailable_message_request
                    requestedAt:
                      type: string
                      format: date-time
                      description: Server time when the request was sent.
                      example: "2026-09-15T10:30:00-03:00"
                    targetChat:
                      type: string
                      description: "`ChatJID` as sent."
                      example: 120363000000000011@g.us
                    targetSender:
                      type: string
                      description: "`SenderJID` as sent (empty when omitted)."
                      example: 5511888888888@s.whatsapp.net
    ChatReplyResponse:
      description: Success envelope of `POST /chat/reply`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the reply send.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the reply was sent.
                  example: true
                message_id:
                  type: string
                  description: ID of the new reply message.
                  example: 3EB0C767D26A1D6B8AA0
                timestamp:
                  type: string
                  format: date-time
                  description: Server timestamp of the send.
                  example: "2026-09-15T10:30:00-03:00"
                to:
                  type: string
                  description: Chat JID parsed from `to`.
                  example: 5511999999999@s.whatsapp.net
                quoted_msg_id:
                  type: string
                  description: Echo of `quoted_msg_id`.
                  example: 3EB0F2A1C9D04E8F1234
                content_type:
                  type: string
                  description: Echo of `content_type`.
                  example: text
    StatusSendResponse:
      description: Success envelope of `POST /status/send/text`, `/image` and `/video`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the status send.
              properties:
                message_id:
                  type: string
                  description: ID of the status message.
                  example: 3EB06F9067F80BAB89FF
                timestamp:
                  type: string
                  format: date-time
                  description: Server timestamp of the send.
                  example: "2026-09-15T10:30:00-03:00"
                status:
                  type: string
                  description: Always `sent`.
                  example: sent
                type:
                  type: string
                  description: Kind of status posted.
                  enum:
                    - text_status
                    - image_status
                    - video_status
                  example: text_status
    StatusSendAudioResponse:
      description: Success envelope of `POST /status/send/audio`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Result of the audio status send.
              properties:
                message_id:
                  type: string
                  description: ID of the status message.
                  example: 3EB072C49492CB69CBCBCF
                timestamp:
                  type: string
                  format: date-time
                  description: Server timestamp of the send.
                  example: "2026-09-14T22:25:15-03:00"
                status:
                  type: string
                  description: Always `sent`.
                  example: sent
                type:
                  type: string
                  description: "`voice_status` when `ptt` is true, `audio_status` otherwise."
                  enum:
                    - voice_status
                    - audio_status
                  example: voice_status
                ptt:
                  type: boolean
                  description: Effective `ptt` value (default applied).
                  example: true
                duration:
                  type: integer
                  description: Detected duration in seconds (0 when it could not be detected).
                  example: 4
                has_waveform:
                  type: boolean
                  description: Always `true`; a waveform is generated for audio statuses.
                  example: true
                mime_type:
                  type: string
                  description: Final MIME type sent to WhatsApp after normalization.
                  example: audio/ogg; codecs=opus
    SyncChatListResponse:
      description: Success envelope of `GET /chat/list`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Chats with stored settings.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                chats:
                  type: array
                  description: Chats ordered pinned first, then non-archived.
                  items:
                    type: object
                    description: Stored settings of one chat.
                    properties:
                      chat_jid:
                        type: string
                        description: Full chat JID.
                        example: 5511999999999@s.whatsapp.net
                      phone_number:
                        type: string
                        description: User part of the JID (phone number, LID or group ID). Omitted when the JID does not parse.
                        example: "5511999999999"
                      muted_until:
                        type: integer
                        format: int64
                        description: Unix time in seconds when the mute ends. Omitted when the chat is not muted.
                        example: 1767225600
                      pinned:
                        type: boolean
                        description: Whether the chat is pinned.
                        example: true
                      archived:
                        type: boolean
                        description: Whether the chat is archived.
                        example: false
                stats:
                  type: object
                  description: Counters over the returned chats.
                  properties:
                    total:
                      type: integer
                      description: Number of chats returned.
                      example: 2
                    pinned_count:
                      type: integer
                      description: Number of pinned chats.
                      example: 1
                    archived_count:
                      type: integer
                      description: Number of archived chats.
                      example: 1
                    muted_count:
                      type: integer
                      description: Number of muted chats.
                      example: 1
    SyncAppStateRequest:
      type: object
      description: Options of the app-state resynchronization. Every field is optional.
      properties:
        full_sync:
          type: boolean
          default: true
          description: "`true` downloads full snapshots; `false` only fetches patches newer than the stored versions."
          example: true
        auto_recovery:
          type: boolean
          default: true
          description: Ask the primary phone for a recovery snapshot of each collection that fails.
          example: true
    SyncAppStateResponse:
      description: Success envelope of `POST /sync/app-state`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Outcome of the five collection fetches.
              properties:
                success:
                  type: boolean
                  description: "`true` only when all five collections synchronized."
                  example: true
                message:
                  type: string
                  description: Human-readable summary of the outcome.
                  example: App state synchronized successfully. All 5 patch types updated.
                results:
                  type: array
                  description: One entry per collection, in fetch order.
                  items:
                    type: object
                    description: Outcome of one collection.
                    properties:
                      name:
                        type: string
                        description: Collection name.
                        enum:
                          - critical_block
                          - critical_unblock_low
                          - regular_low
                          - regular_high
                          - regular
                        example: regular_high
                      success:
                        type: boolean
                        description: Whether this collection synchronized.
                        example: true
                      error:
                        type: string
                        description: Fetch error. Omitted on success.
                        example: "failed to verify patch v1234: mismatching LTHash"
                      duration:
                        type: string
                        description: Time spent on this collection (Go duration format).
                        example: 955.7ms
                      recovery_requested:
                        type: boolean
                        description: "`true` when a recovery request was sent for this failed collection. Omitted otherwise."
                        example: true
                auto_recovery_enabled:
                  type: boolean
                  description: Effective `auto_recovery` value.
                  example: true
    SyncHistoryRequestRequest:
      type: object
      description: Anchor message and size of an on-demand history request for one chat.
      required:
        - phone
        - oldest_message_id
        - oldest_message_timestamp
      properties:
        phone:
          type: string
          description: Chat to page back. Phone number, user JID or group JID.
          example: "5511999999999"
        oldest_message_id:
          type: string
          description: ID of the oldest message already known for this chat.
          example: 3EB0C767D26A1D6B8A9F
        oldest_message_timestamp:
          type: integer
          format: int64
          description: Timestamp of that oldest message in milliseconds since the Unix epoch. Must not be 0.
          example: 1757862000000
        oldest_from_me:
          type: boolean
          default: false
          description: Whether the oldest message was sent by this account.
          example: false
        count:
          type: integer
          format: int32
          default: 50
          description: Number of older messages to ask for. 0 or omitted means 50.
          example: 100
    SyncHistoryRequestResponse:
      description: Success envelope of `POST /sync/history-request`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation of the history request.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: History sync request sent successfully. History will arrive asynchronously via HistorySync events (webhook/websocket).
                request_id:
                  type: string
                  description: Same value as `oldest_message_id`.
                  example: 3EB0C767D26A1D6B8A9F
                chat:
                  type: string
                  description: Chat JID parsed from `phone`.
                  example: 5511999999999@s.whatsapp.net
                count:
                  type: integer
                  description: Effective number of messages requested (default applied).
                  example: 50
    SyncFullHistoryRequest:
      type: object
      description: Limits of the full history sync. Every field is optional.
      properties:
        days_limit:
          type: integer
          format: int32
          default: 30
          description: How many days of history to request. 0 means 30.
          example: 30
        size_mb_limit:
          type: integer
          format: int32
          default: 1000
          description: Size limit in megabytes, also sent as the storage quota. 0 means 1000.
          example: 1000
        include_groups:
          type: boolean
          default: true
          description: Ask for group chat history too.
          example: true
        include_calls:
          type: boolean
          default: false
          description: Ask for the call log history.
          example: false
    SyncFullHistoryResponse:
      description: Success envelope of `POST /sync/full-history`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation of the full history request.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation text.
                  example: Full history sync requested successfully. History will arrive asynchronously via HistorySync events (webhook/websocket). This works exactly like the initial connection sync.
                request_id:
                  type: string
                  description: Random 32-character uppercase hexadecimal request ID.
                  example: 9F3A6C1E2B7D4058A1C3E5F7092B4D6E
                config:
                  type: object
                  description: Effective limits sent to the phone (defaults applied).
                  properties:
                    days_limit:
                      type: integer
                      description: Days of history requested.
                      example: 30
                    size_mb_limit:
                      type: integer
                      description: Size limit in megabytes.
                      example: 1000
                    include_groups:
                      type: boolean
                      description: Whether group history was requested.
                      example: true
                    include_calls:
                      type: boolean
                      description: Whether call log history was requested.
                      example: false
    CallRejectLegacyResponse:
      description: Success envelope of `POST /call/reject/send`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Rejection result.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the rejection stanza was sent.
                  example: true
                call_id:
                  type: string
                  description: Echo of the requested call ID.
                  example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                call_from:
                  type: string
                  description: Echo of `call_from` exactly as sent in the request.
                  example: "5511999999999@s.whatsapp.net"
                reject_type:
                  type: string
                  description: Echo of the requested rejection label.
                  enum:
                    - busy
                    - declined
                    - unavailable
                  example: busy
                action:
                  type: string
                  description: Always `rejected`.
                  enum:
                    - rejected
                  example: rejected
                message_sent:
                  type: boolean
                  description: Present (always `true`) only when `message` was provided, even if sending the text failed.
                  example: true
      example:
        code: 200
        data:
          success: true
          call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
          call_from: "5511999999999@s.whatsapp.net"
          reject_type: busy
          action: rejected
          message_sent: true
        success: true
    CallAcceptV1Response:
      description: Success envelope of `POST /call/accept` and `POST /call/preaccept`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Signaling result.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the stanza was sent.
                  example: true
                call_id:
                  type: string
                  description: Echo of the requested call ID.
                  example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                call_from:
                  type: string
                  description: Caller JID normalized by the gateway.
                  example: "5511999999999@s.whatsapp.net"
                action:
                  type: string
                  description: "`accepted` for `/call/accept`, `preaccepted` for `/call/preaccept`."
                  enum:
                    - accepted
                    - preaccepted
                  example: accepted
      example:
        code: 200
        data:
          success: true
          call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
          call_from: "5511999999999@s.whatsapp.net"
          action: accepted
        success: true
    CallTerminateV1Response:
      description: Success envelope of `POST /call/terminate`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Terminate result.
              properties:
                success:
                  type: boolean
                  description: Always `true` when the stanza was sent.
                  example: true
                call_id:
                  type: string
                  description: Echo of the requested call ID.
                  example: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
                call_from:
                  type: string
                  description: Other party JID normalized by the gateway.
                  example: "5511999999999@s.whatsapp.net"
                reason:
                  type: string
                  description: Reason sent (the default `hangup` when omitted).
                  example: hangup
                action:
                  type: string
                  description: Always `terminated`.
                  enum:
                    - terminated
                  example: terminated
      example:
        code: 200
        data:
          success: true
          call_id: "3F8A2C9B1D7E4F60A1B2C3D4E5F60718"
          call_from: "5511999999999@s.whatsapp.net"
          reason: hangup
          action: terminated
        success: true
    CallIdResponse:
      description: Success envelope returned when a call is placed or joined.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: The new call tracked by the native calls engine.
              properties:
                callId:
                  type: string
                  description: Engine call ID; pass it to `/call/hangup`, `/call/play`, the streams and every other in-call control.
                  example: "call_22A9E1B7D3F84C10"
      example:
        code: 200
        data:
          callId: "call_22A9E1B7D3F84C10"
        success: true
    CallEngineConfigResponse:
      description: Success envelope of `GET /call/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Stored engine configuration. `callProviderToken` is never included.
              properties:
                callsEnabled:
                  type: boolean
                  description: Whether the engine runs for this instance after a reconnect.
                  example: true
                callInboundMode:
                  type: string
                  description: Inbound handling mode (`webhook` when unset).
                  enum:
                    - manual
                    - bot
                    - ivr
                    - ai
                    - webhook
                    - reject
                  example: webhook
                callRecord:
                  type: boolean
                  description: Whether connected calls are recorded automatically.
                  example: true
                callSttUrl:
                  type: string
                  description: Speech-to-text endpoint, empty when unset.
                  example: ""
                callLlmUrl:
                  type: string
                  description: Language-model endpoint, empty when unset.
                  example: ""
                callTtsUrl:
                  type: string
                  description: Text-to-speech endpoint, empty when unset.
                  example: ""
                callSystemPrompt:
                  type: string
                  description: Voice-agent system prompt, empty when unset.
                  example: ""
                callGreeting:
                  type: string
                  description: Voice-agent greeting, empty when unset.
                  example: ""
      example:
        code: 200
        data:
          callsEnabled: true
          callInboundMode: webhook
          callRecord: true
          callSttUrl: ""
          callLlmUrl: ""
          callTtsUrl: ""
          callSystemPrompt: ""
          callGreeting: ""
        success: true
    CallOkResponse:
      description: Success envelope of call controls that only confirm the action.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation.
              properties:
                ok:
                  type: boolean
                  description: Always `true` on success.
                  example: true
      example:
        code: 200
        data:
          ok: true
        success: true
    CallRecordingStopResponse:
      description: Success envelope of `POST /call/record/stop`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Recording result.
              properties:
                ok:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                mediaKey:
                  type: string
                  description: URL of the uploaded WAV (historical field name). Empty when no recording was active.
                  example: "https://my-bucket.s3.amazonaws.com/call-recordings/9b2f6a1e-4c1d-4e0b-8f7a-2d3c4b5a6e7f.wav"
      example:
        code: 200
        data:
          ok: true
          mediaKey: "https://my-bucket.s3.amazonaws.com/call-recordings/9b2f6a1e-4c1d-4e0b-8f7a-2d3c4b5a6e7f.wav"
        success: true
    CallVideoStatsResponse:
      description: Success envelope of `GET /call/{call_id}/video/stats`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Telemetry of the video socket attached to the call.
              properties:
                callId:
                  type: string
                  description: Engine call ID.
                  example: "call_17B3F2A1C9D04E8F"
                framing:
                  type: string
                  description: Inbound framing the socket was opened with.
                  enum:
                    - participant
                    - single-stream
                  example: participant
                uptimeSeconds:
                  type: number
                  description: Seconds since the video socket opened.
                  example: 42.7
                inbound:
                  type: object
                  description: Peer-to-client video counters.
                  properties:
                    participants:
                      type: integer
                      description: Frame queues on the socket, one per camera (1 in single-stream framing).
                      example: 1
                    framesReceived:
                      type: integer
                      description: Access units received from WhatsApp and queued.
                      example: 1260
                    framesDelivered:
                      type: integer
                      description: Access units written to the client.
                      example: 1248
                    framesShed:
                      type: integer
                      description: Queued access units dropped under backpressure or flushed by a newer keyframe.
                      example: 12
                    participantsRejected:
                      type: integer
                      description: Frames refused because the camera cap was reached.
                      example: 0
                    backlogBytes:
                      type: integer
                      description: Bytes currently waiting to be written.
                      example: 0
                    peakBacklogBytes:
                      type: integer
                      description: Largest backlog seen since the socket opened.
                      example: 184320
                    participantCap:
                      type: integer
                      description: Maximum cameras demuxed on one socket.
                      example: 32
                    framesPerSecond:
                      type: number
                      description: Average delivered frames per second since the socket opened. Omitted when uptime is zero.
                      example: 29.2
                    streams:
                      type: array
                      description: Per-camera counters (participant framing only; empty in single-stream framing).
                      items:
                        type: object
                        description: Counters of one camera.
                        properties:
                          id:
                            type: string
                            description: Participant ID used in the frame header.
                            example: "123456789012345@lid"
                          frames:
                            type: integer
                            description: Access units received from this camera.
                            example: 1260
                          bytes:
                            type: integer
                            description: Bytes received from this camera.
                            example: 5120344
                          orientation:
                            type: integer
                            description: Last known orientation (0-3).
                            example: 0
                          ssrc:
                            type: integer
                            description: RTP SSRC of the camera stream.
                            example: 3405691582
                          secondsSinceLastFrame:
                            type: number
                            description: Seconds since the last access unit from this camera.
                            example: 0.03
                          secondsActive:
                            type: number
                            description: Seconds since the first access unit from this camera.
                            example: 42.1
                          fps:
                            type: number
                            description: Average frames per second of this camera.
                            example: 29.9
                outbound:
                  type: object
                  description: Client-to-peer video counters.
                  properties:
                    framesSent:
                      type: integer
                      description: Access units accepted and sent to WhatsApp.
                      example: 1190
                    bytesSent:
                      type: integer
                      description: Bytes accepted and sent.
                      example: 4388120
                    framesDropped:
                      type: integer
                      description: Access units dropped because the call had no video media yet.
                      example: 35
                    framesOversize:
                      type: integer
                      description: Access units ignored for exceeding 1 MiB.
                      example: 0
                    keyframeRequests:
                      type: integer
                      description: Keyframe requests received from the peer.
                      example: 2
                    mediaReady:
                      type: boolean
                      description: Whether outbound video media is live.
                      example: true
                    mediaReadyAfterSeconds:
                      type: number
                      description: Seconds from socket open to the first accepted frame. Omitted until then.
                      example: 1.8
                    framesPerSecond:
                      type: number
                      description: Average sent frames per second since the socket opened. Omitted when uptime is zero.
                      example: 27.9
      example:
        code: 200
        data:
          callId: "call_17B3F2A1C9D04E8F"
          framing: single-stream
          uptimeSeconds: 12.4
          inbound:
            participants: 1
            framesReceived: 360
            framesDelivered: 360
            framesShed: 0
            participantsRejected: 0
            backlogBytes: 0
            peakBacklogBytes: 65536
            participantCap: 32
            framesPerSecond: 29.0
            streams: []
          outbound:
            framesSent: 0
            bytesSent: 0
            framesDropped: 0
            framesOversize: 0
            keyframeRequests: 0
            mediaReady: false
            framesPerSecond: 0
        success: true
    CallParticipantsAddResponse:
      description: Success envelope of `POST /call/participants/add`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Per-target invite result.
              properties:
                ok:
                  type: boolean
                  description: "`true` only when every target was invited."
                  example: true
                failed:
                  type: array
                  description: 'One `"target: reason"` entry per failed target; empty when all succeeded.'
                  items:
                    type: string
                    description: Failed target and the engine reason.
                    example: "123456789012347@lid: meowcaller: participant is already in the call roster"
                  example: []
      example:
        code: 200
        data:
          ok: true
          failed: []
        success: true
    CallLinkCreateResponse:
      description: Success envelope of `POST /call/link/create`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/CallLinkInfo"
      example:
        code: 200
        data:
          token: "Xk3pQ9aB7cD2eF4g"
          url: "https://call.whatsapp.com/video/Xk3pQ9aB7cD2eF4g"
          video: true
        success: true
    CallLinkPreviewResponse:
      description: Success envelope of `GET /call/link/preview`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/CallLinkPreviewInfo"
      example:
        code: 200
        data:
          token: "Xk3pQ9aB7cD2eF4g"
          video: true
          approvalRequired: true
          isAdmin: false
          creator: "123456789012345@lid"
          creatorPhone: "5511988888888@s.whatsapp.net"
        success: true
    CallHandResponse:
      description: Success envelope of `POST /call/hand`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Applied hand state.
              properties:
                ok:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                raised:
                  type: boolean
                  description: Hand state that was applied.
                  example: true
      example:
        code: 200
        data:
          ok: true
          raised: true
        success: true
    CallEnabledToggleResponse:
      description: Success envelope of the call toggles that take `enabled` (`/call/video`, `/call/screenshare`, `/call/waiting-room/approval`).
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Applied toggle value.
              properties:
                ok:
                  type: boolean
                  description: Always `true` on success.
                  example: true
                enabled:
                  type: boolean
                  description: Value that was applied (the default `true` when omitted).
                  example: true
      example:
        code: 200
        data:
          ok: true
          enabled: true
        success: true
    CallGroupStateResponse:
      description: Success envelope of `GET /call/{call_id}/group/state`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/CallGroupState"
      example:
        code: 200
        data:
          callId: "call_5C1E9A7B3D2F4E60"
          transactionId: 4
          participants:
            - jid: "123456789012345@lid"
              phone: "5511988888888@s.whatsapp.net"
              state: connected
        success: true
    CallWaitingRoomResponse:
      description: Success envelope of `GET /call/{call_id}/waiting-room`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/CallWaitingRoomState"
      example:
        code: 200
        data:
          callId: "call_8D4B2E6A1C9F3E70"
          enabled: true
          isAdmin: true
          inWaitingRoom: false
          transactionId: 2
          users: []
        success: true
    UserContactLinkResolveResponse:
      description: Success envelope of `POST /user/contact-link/resolve`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Handler payload with its own success flag and the resolved target.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                data:
                  type: object
                  description: Account the QR link points to.
                  properties:
                    JID:
                      type: string
                      description: JID of the account behind the link.
                      example: 5511999999999@s.whatsapp.net
                    Type:
                      type: string
                      description: Link target type reported by WhatsApp, normally `contact`.
                      example: contact
                    PushName:
                      type: string
                      description: Display (push) name of the account. May be empty.
                      example: Example Store
    UserIdentityCodesResponse:
      description: Success envelope of `GET /user/identity-code/{jid}`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Handler payload with its own success flag and the verification codes.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                data:
                  type: object
                  description: Public identity verification values. No private keys are included.
                  properties:
                    UserID:
                      type: string
                      description: Contact LID the codes were generated for.
                      example: 123456789012345@lid
                    PhoneNumber:
                      type: string
                      description: Contact phone JID when the LID-to-phone mapping is known, otherwise empty.
                      example: 5511999999999@s.whatsapp.net
                    Username:
                      type: string
                      description: Contact username when known, otherwise empty.
                      example: examplestore
                    NumericCode:
                      type: string
                      description: 60-digit security code, the same number shown in the WhatsApp app.
                      example: "123456789012345678901234567890123456789012345678901234567890"
                    DisplayQRCode:
                      type: string
                      format: byte
                      description: Base64 payload of the QR code this account displays for scanning.
                      example: AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJw==
                    VerificationQRCode:
                      type: string
                      format: byte
                      description: Base64 payload expected when scanning the contact's QR code.
                      example: KCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OTw==
    UserUsernameResolveResponse:
      description: Success envelope of `POST /user/username/resolve`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Handler payload with its own success flag and the resolution.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                data:
                  type: object
                  description: Username resolution result.
                  properties:
                    LID:
                      type: string
                      description: LID of the username owner. Empty when `KeyRequired` is true.
                      example: 123456789012345@lid
                    Username:
                      type: string
                      description: Canonical username without `@`. Empty when `KeyRequired` is true.
                      example: examplestore
                    KeyRequired:
                      type: boolean
                      description: "`true` when the owner protects the username with a key and the request had no key or a wrong one."
                      example: false
    UserInfoRequest:
      type: object
      description: Numbers or JIDs to look up. Provide `phone` (preferred) or the legacy `Phone`.
      properties:
        phone:
          description: One phone number/JID as a string, or several as an array. Read before `Phone`.
          oneOf:
            - type: string
              description: Single phone number with country code, or user JID.
              example: "5511999999999"
            - type: array
              description: Several phone numbers or user JIDs.
              items:
                type: string
              example:
                - "5511999999999"
                - "5511988888888"
          example:
            - "5511999999999"
            - "5511988888888"
        Phone:
          description: Legacy spelling of `phone`, used only when `phone` is absent or has no string value.
          oneOf:
            - type: string
              description: Single phone number with country code, or user JID.
              example: "5511999999999"
            - type: array
              description: Several phone numbers or user JIDs.
              items:
                type: string
              example:
                - "5511999999999"
          example:
            - 5511999999999@s.whatsapp.net
    UserInfoDevice:
      type: object
      description: One device registered to the account.
      properties:
        user:
          type: string
          description: User part of the device JID.
          example: "5511999999999"
        agent:
          type: string
          description: Raw agent number of the device JID, as a string.
          example: "0"
        device:
          type: string
          description: Platform name derived from the device ID.
          enum:
            - UNKNOWN
            - CHROME
            - FIREFOX
            - IE
            - OPERA
            - SAFARI
            - EDGE
            - DESKTOP
            - IPAD
            - ANDROID_TABLET
            - OHANA
            - ALOHA
            - CATALINA
            - TCL_TV
          example: SAFARI
        server:
          type: string
          description: JID server of the device.
          example: s.whatsapp.net
        ad:
          type: string
          description: Device address in `user.agent:device@server` form.
          example: 5511999999999.0:5@s.whatsapp.net
        jid:
          type: string
          description: Device JID (`user:device@server`; device 0 has no suffix).
          example: 5511999999999:5@s.whatsapp.net
    UserInfoEntry:
      type: object
      description: Profile data of one queried account.
      properties:
        jid:
          type: string
          description: Account JID.
          example: 5511999999999@s.whatsapp.net
        status:
          type: string
          description: Status (about) text. Empty when hidden or not set.
          example: Available
        picture_id:
          type: string
          description: Current profile picture ID. Empty when there is no picture or it is hidden.
          example: "1700000000"
        verified_name:
          type: string
          description: Verified business name. Omitted for personal accounts.
          example: Example Store
        business_name:
          type: string
          description: Same value as `verified_name`, kept for compatibility. Omitted for personal accounts.
          example: Example Store
        lid:
          type: string
          description: Account LID when WhatsApp reports one. Omitted otherwise.
          example: 123456789012345@lid
        is_business_account:
          type: boolean
          description: "`true` when WhatsApp returned verified-name data for the account."
          example: false
        device_count:
          type: integer
          description: Number of devices registered to the account.
          example: 2
        first_device:
          type: string
          description: JID of the first device in the list (usually the phone). Omitted when there are no devices.
          example: 5511999999999@s.whatsapp.net
        verified_level:
          type: string
          description: Reserved for the business verification level. Currently never filled by this endpoint.
          example: high
        verified_certificate:
          type: object
          description: Present only for business accounts with a certificate.
          properties:
            has_signature:
              type: boolean
              description: "`true` when the certificate carries a signature."
              example: true
            has_server_signature:
              type: boolean
              description: "`true` when the certificate carries a WhatsApp server signature."
              example: true
        devices:
          type: array
          description: Devices registered to the account. `null` when WhatsApp lists none.
          items:
            $ref: "#/components/schemas/UserInfoDevice"
    UserInfoResponse:
      description: Success envelope of `POST /user/info`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Lookup result.
              properties:
                users:
                  type: object
                  description: Profile data keyed by account JID.
                  additionalProperties:
                    $ref: "#/components/schemas/UserInfoEntry"
                  example:
                    5511999999999@s.whatsapp.net:
                      jid: 5511999999999@s.whatsapp.net
                      status: Available
                      picture_id: "1700000000"
                      is_business_account: false
                      device_count: 1
                      first_device: 5511999999999@s.whatsapp.net
                      devices:
                        - user: "5511999999999"
                          agent: "0"
                          device: UNKNOWN
                          server: s.whatsapp.net
                          ad: 5511999999999.0:0@s.whatsapp.net
                          jid: 5511999999999@s.whatsapp.net
    UserCheckResponse:
      description: Success envelope of `POST /user/check`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Check result.
              properties:
                Users:
                  type: array
                  description: One entry per queried number.
                  items:
                    type: object
                    description: Registration data for one number.
                    properties:
                      Query:
                        type: string
                        description: The number as WhatsApp echoed it for this query.
                        example: "5511988888888"
                      IsInWhatsapp:
                        type: boolean
                        description: "`true` when the number has a WhatsApp account."
                        example: true
                      JID:
                        type: string
                        description: Phone-shaped JID of the number, even when WhatsApp answers in LID addressing mode.
                        example: 5511988888888@s.whatsapp.net
                      LID:
                        type: string
                        description: LID of the account, empty when WhatsApp reports none.
                        example: 123456789012346@lid
                      VerifiedName:
                        type: string
                        description: Verified business name, empty for personal accounts.
                        example: Example Store
                      Username:
                        type: string
                        description: WhatsApp username. Omitted when not reported.
                        example: examplestore
                      VerifiedLevel:
                        type: string
                        description: Business verification level. Omitted for personal accounts.
                        example: high
                      DisappearingMode:
                        type: object
                        description: Default disappearing-message setting of the account. Omitted when not reported.
                        properties:
                          enabled:
                            type: boolean
                            description: "`true` when a disappearing-message duration is set."
                            example: true
                          duration_sec:
                            type: integer
                            format: int64
                            description: Duration in seconds; 0 when disabled.
                            example: 604800
                          set_at:
                            type: string
                            format: date-time
                            description: UTC time the setting was applied. Omitted when unknown.
                            example: "2026-07-01T12:34:56Z"
    UserAvatarResponse:
      description: Success envelope of `POST /user/avatar`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Profile picture metadata returned by WhatsApp.
              properties:
                url:
                  type: string
                  description: Temporary CDN URL of the image.
                  example: https://pps.whatsapp.net/v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                id:
                  type: string
                  description: Picture ID; the same value as `picture_id` in `POST /user/info`.
                  example: "1700000000"
                type:
                  type: string
                  description: "`image` for the full picture or `preview` for the thumbnail."
                  enum:
                    - image
                    - preview
                  example: preview
                direct_path:
                  type: string
                  description: CDN path of the image.
                  example: /v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                hash:
                  type: string
                  format: byte
                  nullable: true
                  description: Base64 hash reported by WhatsApp; usually `null`.
                  example: null
    UserPhotoResponse:
      description: Success envelope of `POST /user/photo`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Upload result.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation message.
                  example: Profile photo set successfully
                PictureID:
                  type: string
                  description: ID of the new profile picture.
                  example: "1757862000"
    UserPhotoRemoveResponse:
      description: Success envelope of `POST /user/photo/remove`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Removal result.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation message.
                  example: Profile photo removed successfully
    UserPresenceResponse:
      description: Success envelope of `POST /user/presence`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Confirmation.
              properties:
                Details:
                  type: string
                  description: Fixed confirmation message (spelling as returned by the gateway).
                  example: Presence set successfuly
    UserPrivacySettingsResponse:
      description: Success envelope of `GET /user/privacy/settings`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Privacy settings as reported by WhatsApp; empty string when not reported.
              properties:
                group_add:
                  type: string
                  description: Who can add you to groups.
                  enum: [ all, contacts, contact_blacklist, none, "" ]
                  example: contacts
                last_seen:
                  type: string
                  description: Who can see your last seen.
                  enum: [ all, contacts, contact_blacklist, none, "" ]
                  example: contacts
                status:
                  type: string
                  description: Who can see your status updates.
                  enum: [ all, contacts, contact_blacklist, none, "" ]
                  example: contacts
                profile:
                  type: string
                  description: Who can see your profile photo.
                  enum: [ all, contacts, contact_blacklist, none, "" ]
                  example: all
                read_receipts:
                  type: string
                  description: Whether read receipts are sent.
                  enum: [ all, none, "" ]
                  example: all
                online:
                  type: string
                  description: Who can see when you are online.
                  enum: [ all, match_last_seen, "" ]
                  example: match_last_seen
                call_add:
                  type: string
                  description: Who can call you (`known` silences unknown callers).
                  enum: [ all, known, "" ]
                  example: all
    UserPushNameResponse:
      description: Success envelope of `POST /user/pushname`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Update result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation message.
                  example: Push name updated successfully
                push_name:
                  type: string
                  description: Push name that was set.
                  example: Example Store
    UserContactInfoRequest:
      type: object
      description: Contact to inspect.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Phone number with country code, user JID or LID.
          example: "5511999999999"
    UserContactInfoResponse:
      description: Success envelope of `POST /user/contact/info`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Consolidated contact view. Sections are `null` when the source failed or does not apply.
              properties:
                phone:
                  type: string
                  description: Phone number (user part of the phone JID).
                  example: "5511988888888"
                jid:
                  type: string
                  description: Phone-shaped JID of the contact.
                  example: 5511988888888@s.whatsapp.net
                input_type:
                  type: string
                  description: How the request `phone` was interpreted.
                  enum:
                    - phone
                    - jid
                    - lid
                  example: phone
                lid:
                  type: string
                  nullable: true
                  description: Contact LID when known, otherwise `null`.
                  example: 123456789012346@lid
                is_on_whatsapp:
                  type: boolean
                  description: "`true` when the number has a WhatsApp account."
                  example: true
                verified_name:
                  type: string
                  description: Verified business name. Omitted for personal accounts.
                  example: Example Store
                user_info:
                  type: object
                  nullable: true
                  description: WhatsApp user info, or `null` when unavailable.
                  properties:
                    status:
                      type: string
                      description: Status (about) text.
                      example: Open Monday to Friday
                    picture_id:
                      type: string
                      description: Current profile picture ID; empty when none.
                      example: "1728656243"
                    verified_name:
                      type: string
                      description: Verified business name. Omitted for personal accounts.
                      example: Example Store
                    is_business_account:
                      type: boolean
                      description: "`true` when verified-name data exists."
                      example: true
                    device_count:
                      type: integer
                      description: Number of registered devices.
                      example: 1
                devices:
                  type: array
                  nullable: true
                  description: Registered devices, or `null` when unavailable.
                  items:
                    $ref: "#/components/schemas/UserInfoDevice"
                avatar:
                  type: object
                  nullable: true
                  description: Profile picture data, or `null` when there is no picture, it is hidden, or the lookup failed.
                  properties:
                    url:
                      type: string
                      description: Temporary CDN URL of the picture (full size, or the preview when only the preview was available).
                      example: https://pps.whatsapp.net/v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                    id:
                      type: string
                      description: Picture ID.
                      example: "1728656243"
                    type:
                      type: string
                      description: "`image` or `preview`."
                      example: image
                    direct_path:
                      type: string
                      description: CDN path of the picture.
                      example: /v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                    hash:
                      type: string
                      format: byte
                      description: Base64 hash reported by WhatsApp. Omitted when empty.
                      example: 3q2+7w==
                    base64:
                      type: string
                      description: Picture downloaded by the gateway as a data URL. Omitted when the download failed.
                      example: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2Q==
                    width:
                      type: integer
                      description: Picture width in pixels. Omitted when unknown.
                      example: 640
                    height:
                      type: integer
                      description: Picture height in pixels. Omitted when unknown.
                      example: 640
                    preview_url:
                      type: string
                      description: Thumbnail URL. Present only when both full and preview lookups succeeded.
                      example: https://pps.whatsapp.net/v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?stp=dst-jpg_s96x96&ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                    preview_direct_path:
                      type: string
                      description: Thumbnail CDN path. Present only with `preview_url`.
                      example: /v/t61.24694-24/123456789_1234567890123456_1234567890123456789_n.jpg?stp=dst-jpg_s96x96&ccb=11-4&oh=01_Q5AaIExample&oe=68E0A1B2
                contact_names:
                  type: object
                  nullable: true
                  description: Names from the local contact store, or `null` when the contact is not stored.
                  properties:
                    first_name:
                      type: string
                      description: First name from the address book.
                      example: Example
                    full_name:
                      type: string
                      description: Full name from the address book.
                      example: Example Store
                    push_name:
                      type: string
                      description: Name the contact set for themselves.
                      example: Example Store
                    business_name:
                      type: string
                      description: Verified business name stored locally.
                      example: Example Store
                business_profile:
                  type: object
                  nullable: true
                  description: Business profile, or `null` for personal accounts and lookup failures.
                  properties:
                    address:
                      type: string
                      description: Business address.
                      example: 100 Example Avenue, Sao Paulo
                    email:
                      type: string
                      description: Business email.
                      example: contact@example.com
                    business_hours_timezone:
                      type: string
                      description: IANA time zone of the business hours.
                      example: America/Sao_Paulo
                    categories:
                      type: array
                      nullable: true
                      description: Business categories.
                      items:
                        $ref: "#/components/schemas/UserBusinessCategory"
                    profile_options:
                      type: object
                      description: Raw profile options reported by WhatsApp.
                      additionalProperties:
                        type: string
                      example:
                        commerce_experience: catalog
                    business_hours:
                      type: array
                      nullable: true
                      description: Opening hours per weekday.
                      items:
                        $ref: "#/components/schemas/UserBusinessHours"
    UserBusinessCategory:
      type: object
      description: One business category.
      properties:
        id:
          type: string
          description: Category ID.
          example: "133436743388217"
        name:
          type: string
          description: Category name in the language WhatsApp returned.
          example: Shopping & Retail
    UserBusinessHours:
      type: object
      description: Opening hours for one weekday.
      properties:
        day_of_week:
          type: string
          description: Three-letter weekday.
          example: mon
        mode:
          type: string
          description: Opening mode reported by WhatsApp, for example `specific_hours`, `open_24h` or `appointment_only`.
          example: specific_hours
        open_time:
          type: string
          description: Opening time as `HH:MM`; empty when the mode has no hours.
          example: "09:00"
        close_time:
          type: string
          description: Closing time as `HH:MM`; empty when the mode has no hours.
          example: "18:00"
    UserContactAddRequest:
      type: object
      description: Contact to save.
      required:
        - phone
        - full_name
      properties:
        phone:
          type: string
          description: Phone number with country code or user JID.
          example: "5511999999999"
        full_name:
          type: string
          description: Name to save in the address book. Cannot be empty.
          minLength: 1
          example: Maria Silva
    UserContactAddResponse:
      description: Success envelope of `POST /user/contact/add`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Save result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation message.
                  example: Contact added successfully
                phone:
                  type: string
                  description: "`phone` exactly as sent."
                  example: "5511999999999"
                full_name:
                  type: string
                  description: Name that was saved.
                  example: Maria Silva
    UserContactRemoveRequest:
      type: object
      description: Contact to remove.
      required:
        - phone
      properties:
        phone:
          type: string
          description: Phone number with country code or user JID.
          example: "5511999999999"
    UserContactRemoveResponse:
      description: Success envelope of `POST /user/contact/remove`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Removal result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation message.
                  example: Contact removed successfully
                phone:
                  type: string
                  description: "`phone` exactly as sent."
                  example: "5511999999999"
    UserStatusGetResponse:
      description: Success envelope of `GET /user/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Status text.
              properties:
                status:
                  type: string
                  description: Current about text; empty when not set.
                  example: Available for support 9am-6pm
    UserStatusSetResponse:
      description: Success envelope of `POST /user/status`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Update result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Fixed confirmation message.
                  example: Status updated successfully
                status:
                  type: string
                  description: Status text that was set.
                  example: Available for support 9am-6pm
    UserUpdateRequest:
      type: object
      description: Gateway user fields to change. At least one must differ from the current value.
      properties:
        name:
          type: string
          description: New gateway user name, up to 255 characters.
          maxLength: 255
          example: sales-team
        token:
          type: string
          description: New API token, at least 8 characters, unique across users.
          minLength: 8
          example: 9f8e7d6c5b4a3210fedcba98
    UserUpdateResponse:
      description: Success envelope of `POST /user/update`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Updated gateway user record.
              properties:
                id:
                  type: string
                  description: Gateway user ID.
                  example: 3f0b9f2c8e7d4a6b9c1d2e3f4a5b6c7d
                name:
                  type: string
                  description: Final user name.
                  example: sales-team
                token:
                  type: string
                  description: Final API token (new or unchanged). Secret.
                  example: 9f8e7d6c5b4a3210fedcba98
                message:
                  type: string
                  description: Fixed confirmation message.
                  example: Profile updated successfully
    UserBusinessProfileResponse:
      description: Success envelope of `POST /user/business/profile`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Business profile data.
              properties:
                jid:
                  type: string
                  description: Account JID.
                  example: 5511988888888@s.whatsapp.net
                email:
                  type: string
                  description: Business email; empty when not available.
                  example: contact@example.com
                address:
                  type: string
                  description: Business address; empty when not available.
                  example: 100 Example Avenue, Sao Paulo
                categories:
                  type: array
                  nullable: true
                  description: Business categories.
                  items:
                    $ref: "#/components/schemas/UserBusinessCategory"
                profile_options:
                  type: object
                  nullable: true
                  description: Raw profile options reported by WhatsApp.
                  additionalProperties:
                    type: string
                  example:
                    commerce_experience: catalog
                business_hours_timezone:
                  type: string
                  description: IANA time zone of the business hours.
                  example: America/Sao_Paulo
                business_hours:
                  type: array
                  nullable: true
                  description: Opening hours per weekday.
                  items:
                    $ref: "#/components/schemas/UserBusinessHours"
                verified_name:
                  type: string
                  description: Verified business name. Omitted when empty.
                  example: Example Store
                business_name:
                  type: string
                  description: Declared in the response type but never filled by this endpoint (omitted).
                  example: Example Store
                status:
                  type: string
                  description: Status (about) text. Omitted when empty.
                  example: Open Monday to Friday
                picture_id:
                  type: string
                  description: Profile picture ID. Omitted when empty.
                  example: "1728656243"
                is_business_account:
                  type: boolean
                  description: "`true` when a verified name or a business profile was found."
                  example: true
                profile_data_available:
                  type: boolean
                  description: "`true` when the business profile fields were read successfully."
                  example: true
    UserContactsResponse:
      description: Success envelope of `GET /user/contacts`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Contacts keyed by JID.
              additionalProperties:
                type: object
                description: Names stored for one contact.
                properties:
                  Found:
                    type: boolean
                    description: Always `true` for listed contacts.
                    example: true
                  FirstName:
                    type: string
                    description: First name from the address book.
                    example: Maria
                  FullName:
                    type: string
                    description: Full name from the address book.
                    example: Maria Silva
                  PushName:
                    type: string
                    description: Name the contact set for themselves.
                    example: Maria
                  BusinessName:
                    type: string
                    description: Verified business name.
                    example: Example Store
                  Username:
                    type: string
                    description: WhatsApp username when known.
                    example: examplestore
                  RedactedPhone:
                    type: string
                    description: Masked phone number, only for LID members seen in groups.
                    example: "+55∙∙∙∙∙∙∙∙88"
              example:
                5511999999999@s.whatsapp.net:
                  Found: true
                  FirstName: Maria
                  FullName: Maria Silva
                  PushName: Maria
                  BusinessName: ""
                  Username: ""
                  RedactedPhone: ""
    UserLIDGetResponse:
      description: Success envelope of `GET|POST /user/lid/get`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Lookup result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                phone:
                  type: string
                  description: Phone JID parsed from the input.
                  example: 5511999999999@s.whatsapp.net
                lid:
                  type: string
                  description: Mapped LID; empty when `has_lid` is false.
                  example: 123456789012345@lid
                has_lid:
                  type: boolean
                  description: "`true` when the local store has a LID for this number."
                  example: true
    UserJIDFromLIDResponse:
      description: Success envelope of `GET|POST /user/lid/from-lid`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Lookup result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                lid:
                  type: string
                  description: LID parsed from the input.
                  example: 123456789012345@lid
                phone:
                  type: string
                  description: Phone JID mapped to the LID.
                  example: 5511999999999@s.whatsapp.net
    DeviceUsersResponse:
      description: Success envelope of `POST /device/users`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Device list.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                devices:
                  type: array
                  nullable: true
                  description: Devices of all requested accounts; `null` when none were found.
                  items:
                    $ref: "#/components/schemas/DeviceInfo"
                count:
                  type: integer
                  description: Number of devices returned.
                  example: 3
    DeviceUsersContextRequest:
      type: object
      description: Accounts whose devices are listed, with an optional timeout and tag.
      required:
        - user_jids
      properties:
        user_jids:
          type: array
          description: Phone numbers with country code, phone JIDs or LIDs. At least one item.
          minItems: 1
          items:
            type: string
            description: Phone number, phone JID or LID.
            example: 5511999999999@s.whatsapp.net
          example:
            - 5511999999999@s.whatsapp.net
        context:
          type: string
          description: Free-text tag echoed in the response and logs.
          example: crm-sync
        timeout:
          type: integer
          format: int64
          default: 30000000000
          description: Query timeout in nanoseconds (Go `time.Duration`). `0` or omitted means 30 seconds.
          example: 10000000000
    DeviceUsersContextResponse:
      description: Success envelope of `POST /device/users/context`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Device list and echoed tag.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                devices:
                  type: array
                  nullable: true
                  description: Devices of all requested accounts; `null` when none were found.
                  items:
                    $ref: "#/components/schemas/DeviceInfo"
                count:
                  type: integer
                  description: Number of devices returned.
                  example: 2
                context:
                  type: string
                  description: The request `context`, or an empty string.
                  example: crm-sync
    PrivacySetSettingResponse:
      description: Success envelope of `POST /privacy/settings`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Change result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Confirmation built from the request values.
                  example: Privacy setting last_seen updated to contacts
                setting:
                  type: string
                  description: "`setting` exactly as sent."
                  example: last_seen
                value:
                  type: string
                  description: "`value` exactly as sent."
                  example: contacts
    PrivacyDisappearingTimerResponse:
      description: Success envelope of `POST /privacy/disappearing-timer`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Update result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200.
                  example: true
                message:
                  type: string
                  description: Confirmation built from the request value.
                  example: Default disappearing timer set to 7d
                timer:
                  type: string
                  description: "`timer` exactly as sent."
                  example: 7d
    PrivacyBlocklistUpdateResponse:
      description: Success envelope of `POST /privacy/blocklist`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Update result.
              properties:
                success:
                  type: boolean
                  description: Always `true` on 200, even when some contacts failed.
                  example: true
                message:
                  type: string
                  description: Confirmation counting every requested contact.
                  example: Successfully blocked 2 contact(s)
                action:
                  type: string
                  description: "`action` exactly as sent."
                  example: block
                users:
                  type: array
                  description: "`users` exactly as sent."
                  items:
                    type: string
                  example:
                    - "5511977777777"
                    - 5511966666666@s.whatsapp.net
    GroupHistorySendFailureResponse:
      description: "502 body of `POST /group/history/send`. Legacy envelope: top-level `success` stays `true`; `data.success` is `false` and `data.error` carries the fixed do-not-retry hint."
      allOf:
        - $ref: "#/components/schemas/LegacyErrorEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Outcome of the history send.
              properties:
                success:
                  type: boolean
                  description: "`true` only when both messages were acknowledged. `false` on 502."
                  example: true
                error:
                  type: string
                  description: Present only on 502. Fixed hint that the IDs must not be retried.
                  example: history operation failed or remains unconfirmed; do not retry reserved IDs
                result:
                  $ref: "#/components/schemas/GroupHistorySendResult"
                retry_safe:
                  type: boolean
                  description: Always `false`. The reserved IDs can never be reused.
                  example: false
                recipient_delivery_verified:
                  type: boolean
                  description: Always `false`. Server acknowledgements do not prove recipient delivery.
                  example: false
                evidence_persisted:
                  type: boolean
                  description: "`true` when the terminal state of the reservation was stored."
                  example: true
    AdminErrorWithDetails:
      description: Error envelope with an additional human-readable `details` hint, written by admin handlers for some failures.
      allOf:
        - $ref: "#/components/schemas/ErrorEnvelope"
        - type: object
          properties:
            details:
              type: string
              description: Extra context for the error. Present only for the failures that set it.
              example: "No user found with ID: bec45bb93cbd24cbec32941ec3c93a12"
    AdminUserListResponse:
      description: Success envelope of `GET /admin/users` and `GET /admin/users/{id}`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: array
              description: Users ordered by name. Empty when there is no match.
              items:
                $ref: "#/components/schemas/User"
    AdminUserProxyConfig:
      type: object
      description: Legacy proxy settings of the user.
      properties:
        enabled:
          type: boolean
          description: In list routes, `true` when `proxy_url` is not empty. In the create response, the `proxyConfig.enabled` value sent.
          example: false
        proxy_url:
          type: string
          description: Stored proxy URL (`socks5://` or `http://`).
          example: socks5://proxy.example.com:1080
    AdminUserS3Config:
      type: object
      description: Per-user S3 media storage settings. The secret key is never returned.
      properties:
        enabled:
          type: boolean
          description: Media is uploaded to S3.
          example: true
        endpoint:
          type: string
          description: S3 endpoint URL.
          example: https://s3.us-east-1.amazonaws.com
        region:
          type: string
          description: Bucket region.
          example: us-east-1
        bucket:
          type: string
          description: Bucket name.
          example: zuckzapgo-media
        access_key:
          type: string
          description: Access key ID, returned unmasked.
          example: AKIAEXAMPLEACCESSKEY
        path_style:
          type: boolean
          description: Path-style addressing (MinIO and compatible services).
          example: false
        public_url:
          type: string
          description: Public base URL used to build media links. Empty uses the endpoint.
          example: https://cdn.example.com
        media_delivery:
          type: string
          description: How media reaches events (`base64`, `url`, `s3` or `both`).
          example: both
        retention_days:
          type: integer
          description: Object retention in days (0 keeps objects forever).
          example: 30
        disable_acl:
          type: boolean
          description: Skips object ACLs, required for buckets with "Bucket owner enforced" ownership.
          example: true
    AdminUserRabbitMQConfig:
      type: object
      description: Per-user RabbitMQ publisher settings as stored.
      properties:
        enabled:
          type: boolean
          description: Events are published to RabbitMQ.
          example: true
        url:
          type: string
          description: AMQP connection URL, returned as stored (the password is not masked).
          example: amqp://zuckzapgo:example-password@rabbitmq.internal:5672/
        exchange:
          type: string
          description: Exchange name.
          example: zuckzapgo.store-support
        exchange_type:
          type: string
          description: Exchange type (`topic`, `direct`, `fanout` or `headers`).
          example: topic
        queue:
          type: string
          description: Queue name.
          example: zuckzapgo.store-support.events
        queue_type:
          type: string
          description: Queue type (`classic`, `quorum` or `stream`).
          example: quorum
        routing_key:
          type: string
          description: Routing key pattern.
          example: events.#
        events:
          type: string
          description: Comma-separated event types published, or `All`.
          example: All
        durable:
          type: boolean
          description: Exchange and queue survive broker restarts.
          example: true
        auto_delete:
          type: boolean
          description: Exchange and queue are deleted when no longer used.
          example: false
        exclusive:
          type: boolean
          description: Queue is exclusive to its connection.
          example: false
        no_wait:
          type: boolean
          description: Declarations do not wait for broker confirmation.
          example: false
        delivery_mode:
          type: integer
          description: 1 non-persistent, 2 persistent (0 when not set).
          example: 2
        dead_letter_exchange:
          type: string
          description: Value of `x-dead-letter-exchange`.
          example: dlx.zuckzapgo
        dead_letter_routing_key:
          type: string
          description: Value of `x-dead-letter-routing-key`.
          example: dlq.events
        message_ttl:
          type: integer
          format: int64
          description: Value of `x-message-ttl` in milliseconds (0 when not set).
          example: 86400000
        max_length:
          type: integer
          format: int64
          description: Value of `x-max-length` (0 when not set).
          example: 100000
        max_length_bytes:
          type: integer
          format: int64
          description: Value of `x-max-length-bytes` (0 when not set).
          example: 104857600
        queue_arguments:
          type: string
          description: Custom queue arguments as a JSON-encoded string in list routes. The create response returns the object sent (or null).
          example: '{"x-overflow":"reject-publish"}'
        exchange_arguments:
          type: string
          description: Custom exchange arguments as a JSON-encoded string in list routes. The create response returns the object sent (or null).
          example: "{}"
    AdminUserWhatsAppConfig:
      type: object
      description: Client identity overrides stored for the user and used when it connects. Empty values use the gateway defaults.
      properties:
        wa_version:
          type: string
          description: WhatsApp Web version to report.
          example: 2.3000.1026436087
        wa_platform:
          type: string
          description: Platform to report.
          example: WEB
        wa_release_channel:
          type: string
          description: Release channel to report.
          example: RELEASE
        wa_web_sub_platform:
          type: string
          description: Web sub-platform to report.
          example: WEB_BROWSER
        wa_os_name:
          type: string
          description: Operating system name to report.
          example: Mac OS 10
        wa_os_version:
          type: string
          description: Operating system version to report.
          example: 10.15.7
        wa_device_name:
          type: string
          description: Device name to report.
          example: Desktop
        wa_manufacturer:
          type: string
          description: Device manufacturer to report.
          example: Apple
        wa_device_board:
          type: string
          description: Device board identifier to report.
          example: Mac-123456
        wa_locale_language:
          type: string
          description: Locale language (ISO 639-1).
          example: en
        wa_locale_country:
          type: string
          description: Locale country (ISO 3166-1 alpha-2).
          example: US
        wa_mcc:
          type: string
          description: Mobile country code.
          example: "000"
        wa_mnc:
          type: string
          description: Mobile network code.
          example: "000"
        wa_connect_type:
          type: string
          description: Connection type to report.
          example: WIFI_UNKNOWN
        wa_platform_type:
          type: string
          description: Platform type to report.
          example: DESKTOP
    CreateUserProxyConfig:
      type: object
      description: Legacy WhatsApp proxy. Only `proxyURL` is stored; must stay empty when `networkRoute` is present.
      properties:
        enabled:
          type: boolean
          description: Echoed in the response only; the proxy is used whenever `proxyURL` is set.
          example: true
        proxyURL:
          type: string
          description: Proxy URL (`socks5://user:pass@host:port` or `http://host:port`).
          example: socks5://proxy.example.com:1080
    CreateUserS3Config:
      type: object
      description: S3 media storage. snake_case keys are preferred; the camelCase aliases are used only when the snake_case key is absent.
      properties:
        enabled:
          type: boolean
          description: Upload media to S3 and initialize the per-user S3 client right away.
          example: true
        endpoint:
          type: string
          description: S3 endpoint URL (AWS or compatible service).
          example: https://s3.us-east-1.amazonaws.com
        region:
          type: string
          description: Bucket region.
          example: us-east-1
        bucket:
          type: string
          description: Bucket name.
          example: zuckzapgo-media
        access_key:
          type: string
          description: Access key ID.
          example: AKIAEXAMPLEACCESSKEY
        accessKey:
          type: string
          description: camelCase alias of `access_key`.
          example: AKIAEXAMPLEACCESSKEY
        secret_key:
          type: string
          description: Secret access key. Stored, never returned.
          example: exampleSecretKey0123456789
        secretKey:
          type: string
          description: camelCase alias of `secret_key`.
          example: exampleSecretKey0123456789
        path_style:
          type: boolean
          description: Use path-style addressing (MinIO and compatible services).
          example: false
        pathStyle:
          type: boolean
          description: camelCase alias of `path_style`.
          example: false
        public_url:
          type: string
          description: Public base URL used to build media links.
          example: https://cdn.example.com
        publicUrl:
          type: string
          description: camelCase alias of `public_url`.
          example: https://cdn.example.com
        media_delivery:
          type: string
          description: How media reaches events. `s3` and `url` are equivalent.
          enum:
            - base64
            - url
            - s3
            - both
          example: both
        mediaDelivery:
          type: string
          description: camelCase alias of `media_delivery`.
          example: both
        retention_days:
          type: integer
          description: Object retention in days (0 keeps objects forever).
          example: 30
        retentionDays:
          type: integer
          description: camelCase alias of `retention_days`.
          example: 30
        disable_acl:
          type: boolean
          description: Skip object ACLs, required for buckets with "Bucket owner enforced" ownership.
          example: true
        disableAcl:
          type: boolean
          description: camelCase alias of `disable_acl`.
          example: true
    CreateUserRabbitMQConfig:
      type: object
      description: Per-user RabbitMQ publisher, stored as sent (no defaults or validation here, unlike `POST /session/rabbitmq/config`). With `enabled` true the client is initialized right away; failures are only logged.
      properties:
        enabled:
          type: boolean
          description: Publish this user's events to RabbitMQ.
          example: true
        url:
          type: string
          description: AMQP connection URL.
          example: amqp://zuckzapgo:example-password@rabbitmq.internal:5672/
        exchange:
          type: string
          description: Exchange name.
          example: zuckzapgo.store-support
        exchange_type:
          type: string
          description: Exchange type.
          enum:
            - topic
            - direct
            - fanout
            - headers
          example: topic
        queue:
          type: string
          description: Queue name.
          example: zuckzapgo.store-support.events
        queue_type:
          type: string
          description: Queue type.
          enum:
            - classic
            - quorum
            - stream
          example: quorum
        routing_key:
          type: string
          description: Routing key pattern.
          example: events.#
        events:
          type: string
          description: Comma-separated event types to publish, or `All`. Not validated here.
          example: All
        durable:
          type: boolean
          description: Declare durable exchange and queue.
          example: true
        auto_delete:
          type: boolean
          description: Declare auto-delete exchange and queue.
          example: false
        exclusive:
          type: boolean
          description: Declare an exclusive queue.
          example: false
        no_wait:
          type: boolean
          description: Do not wait for declaration confirmations.
          example: false
        delivery_mode:
          type: integer
          minimum: 0
          maximum: 255
          description: 1 non-persistent, 2 persistent. Values above 255 make the body invalid (400).
          example: 2
        dead_letter_exchange:
          type: string
          description: Sets `x-dead-letter-exchange` on the queue.
          example: dlx.zuckzapgo
        dead_letter_routing_key:
          type: string
          description: Sets `x-dead-letter-routing-key` on the queue.
          example: dlq.events
        message_ttl:
          type: integer
          format: int64
          description: Sets `x-message-ttl` in milliseconds.
          example: 86400000
        max_length:
          type: integer
          format: int64
          description: Sets `x-max-length`.
          example: 100000
        max_length_bytes:
          type: integer
          format: int64
          description: Sets `x-max-length-bytes`.
          example: 104857600
        queue_arguments:
          type: object
          additionalProperties: true
          description: Extra queue arguments as a JSON object.
          example:
            x-overflow: reject-publish
        exchange_arguments:
          type: object
          additionalProperties: true
          description: Extra exchange arguments as a JSON object.
          example:
            alternate-exchange: zuckzapgo.unrouted
    CreateUserWhatsAppConfig:
      type: object
      description: Optional client identity overrides stored for the user and used when it connects. Omitted fields are stored empty (gateway defaults apply).
      properties:
        waVersion:
          type: string
          description: WhatsApp Web version to report.
          example: 2.3000.1026436087
        waPlatform:
          type: string
          description: Platform to report.
          enum:
            - WEB
            - ANDROID
            - IOS
          example: WEB
        waReleaseChannel:
          type: string
          description: Release channel to report.
          enum:
            - RELEASE
            - BETA
          example: RELEASE
        waWebSubPlatform:
          type: string
          description: Web sub-platform to report.
          enum:
            - WEB_BROWSER
            - DARWIN
          example: WEB_BROWSER
        waOSName:
          type: string
          description: Operating system name to report.
          example: Mac OS 10
        waOSVersion:
          type: string
          description: Operating system version to report.
          example: 10.15.7
        waDeviceName:
          type: string
          description: Device name to report.
          example: Desktop
        waManufacturer:
          type: string
          description: Device manufacturer to report.
          example: Apple
        waDeviceBoard:
          type: string
          description: Device board identifier to report.
          example: Mac-123456
        waLocaleLanguage:
          type: string
          description: Locale language (ISO 639-1).
          example: en
        waLocaleCountry:
          type: string
          description: Locale country (ISO 3166-1 alpha-2).
          example: US
        waMCC:
          type: string
          description: Mobile country code.
          example: "000"
        waMNC:
          type: string
          description: Mobile network code.
          example: "000"
        waConnectType:
          type: string
          description: Connection type to report.
          enum:
            - WIFI_UNKNOWN
            - CELLULAR_LTE
            - CELLULAR_3G
            - CELLULAR_2G
          example: WIFI_UNKNOWN
        waPlatformType:
          type: string
          description: Platform type to report.
          enum:
            - DESKTOP
            - CHROME
            - FIREFOX
            - SAFARI
            - EDGE
          example: DESKTOP
    AdminCreatedUser:
      type: object
      description: Settings stored for the new user, echoed in snake_case. The S3 secret key is never included.
      properties:
        id:
          type: string
          description: Generated user ID (32 hexadecimal characters). Use it in the other `/admin/users/{id}` routes.
          example: bec45bb93cbd24cbec32941ec3c93a12
        name:
          type: string
          description: Display name sent.
          example: store-support
        token:
          type: string
          description: API token sent.
          example: a1b2c3d4e5f6a7b8c9d0
        webhook:
          type: string
          description: Webhook URL sent (empty when omitted).
          example: https://hooks.example.com/zuckzapgo
        expiration:
          type: integer
          description: Expiration value sent (0 when omitted).
          example: 0
        events:
          type: string
          description: Event subscription sent.
          example: All
        proxy_config:
          $ref: "#/components/schemas/AdminUserProxyConfig"
        s3_config:
          $ref: "#/components/schemas/AdminUserS3Config"
        rabbitmq_config:
          $ref: "#/components/schemas/AdminUserRabbitMQConfig"
        whatsapp_config:
          $ref: "#/components/schemas/AdminUserWhatsAppConfig"
        skip_media_download:
          type: boolean
          description: Value of `skipMedia`.
          example: false
        skip_groups:
          type: boolean
          description: Value of `skipGroups`.
          example: false
        skip_newsletters:
          type: boolean
          description: Value of `skipNewsletters`.
          example: false
        skip_broadcasts:
          type: boolean
          description: Value of `skipBroadcasts`.
          example: false
        skip_own_messages:
          type: boolean
          description: Value of `skipOwnMessages`.
          example: false
        echo_api_messages:
          type: boolean
          description: Value of `echoApiMessages`.
          example: false
        skip_calls:
          type: boolean
          description: Value of `skipCalls`.
          example: false
        call_reject_message:
          type: string
          description: Stored call rejection message (default applied).
          example: Sorry, I cannot take calls at the moment.
        call_reject_type:
          type: string
          description: Stored call rejection type (default applied).
          example: busy
        globalTransportSkips:
          $ref: "#/components/schemas/GlobalTransportSkipFlags"
        isFromAPI:
          type: boolean
          description: Mirrors `echo_api_messages`.
          example: false
        auto_presence_unavailable:
          type: boolean
          description: Value of `autoPresenceUnavailable`.
          example: false
        network_route:
          $ref: "#/components/schemas/NetworkRouteView"
    AdminInstanceStatusResponse:
      description: Success envelope of `GET /admin/instances/status`, with top-level pagination.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: array
              description: Instance status entries for this page.
              items:
                $ref: "#/components/schemas/AdminInstanceStatus"
            pagination:
              $ref: "#/components/schemas/AdminInstanceStatusPagination"
    AdminInstanceStatus:
      type: object
      description: Connection status of one instance.
      properties:
        id:
          type: string
          description: Stored user ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        name:
          type: string
          description: Display name.
          example: store-support
        jid:
          type: string
          description: Paired device JID, empty before pairing.
          example: "5511999999999:12@s.whatsapp.net"
        qrcode:
          type: string
          description: Stored pairing QR code (base64 PNG data URL); empty while connected and logged in.
          example: ""
        qrcode_text:
          type: string
          description: Raw QR code text; empty while connected and logged in.
          example: ""
        connected:
          type: boolean
          description: Socket connected (runtime), or the stored flag when there is no client.
          example: true
        loggedIn:
          type: boolean
          description: Session logged in; always `false` when there is no client.
          example: true
        hasClient:
          type: boolean
          description: This process holds a WhatsApp client for the instance.
          example: true
        connectionHealth:
          type: string
          description: Summary state.
          enum:
            - connected
            - connected_not_logged
            - disconnected
            - no_client
          example: connected
        lastSuccessfulConnect:
          type: integer
          format: int64
          description: Unix time (seconds) of the last successful connection; 0 when unknown.
          example: 1757862000
        autoReconnectErrors:
          type: integer
          description: Consecutive automatic reconnect errors.
          example: 0
        enableAutoReconnect:
          type: boolean
          description: Automatic reconnect is enabled on the client.
          example: true
        source:
          type: string
          description: Where the flags came from.
          enum:
            - runtime
            - database
          example: runtime
        timestamp:
          type: string
          format: date-time
          description: UTC time the page was built (RFC 3339 with nanoseconds).
          example: "2026-09-15T12:00:00.123456789Z"
    AdminInstanceStatusPagination:
      type: object
      description: Pagination metadata of the instance status page.
      properties:
        limit:
          type: integer
          description: Effective page size.
          example: 200
        offset:
          type: integer
          description: Offset used.
          example: 0
        has_more:
          type: boolean
          description: More instances exist after this page.
          example: true
        next_offset:
          type: integer
          nullable: true
          description: Offset of the next page, or null when there is none.
          example: 200
        total:
          type: integer
          description: Total matching instances; only with `include_total=true`.
          example: 250
    AdminUserPrefsRequest:
      type: object
      description: Event delivery preferences to change. Send only the flags to update; at least one is required.
      minProperties: 1
      properties:
        skipMedia:
          type: boolean
          description: Do not download or inline media in events (column `skip_media_download`).
          example: true
        skipGroups:
          type: boolean
          description: Skip events from group chats.
          example: true
        skipNewsletters:
          type: boolean
          description: Skip events from newsletters (channels).
          example: true
        skipBroadcasts:
          type: boolean
          description: Skip broadcast and status events.
          example: true
        skipOwnMessages:
          type: boolean
          description: Skip messages sent by the instance itself.
          example: false
        echoApiMessages:
          type: boolean
          description: Emit synthetic Message events for messages sent through the API (also requires `ECHO_API_MESSAGES_ENABLED=true`).
          example: false
        skipCalls:
          type: boolean
          description: Skip call events.
          example: false
    AdminUserPrefsResponse:
      description: Success envelope of `PUT /admin/users/{id}`. There is no `data` key.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            updated_fields:
              type: integer
              description: Number of preference columns written.
              example: 1
    AdminUserDeleteResponse:
      description: Success envelope of `DELETE /admin/users/{id}`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Identifier of the deleted user.
              properties:
                id:
                  type: string
                  description: Deleted user ID.
                  example: bec45bb93cbd24cbec32941ec3c93a12
            details:
              type: string
              description: Confirmation text.
              example: user deleted successfully
    AdminUserFullDeleteResponse:
      description: Success envelope of `DELETE /admin/users/{id}/full`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Identity of the removed user, read before deletion.
              properties:
                id:
                  type: string
                  description: Removed user ID.
                  example: bec45bb93cbd24cbec32941ec3c93a12
                name:
                  type: string
                  description: Display name; empty when the configuration could not be read.
                  example: store-support
                jid:
                  type: string
                  description: Paired device JID; empty when never paired or not readable.
                  example: "5511999999999:12@s.whatsapp.net"
            details:
              type: string
              description: Confirmation text.
              example: user instance removed completely (all phases completed)
    AdminLicenseInfo:
      type: object
      description: License state returned as a raw object (no envelope).
      properties:
        valid:
          type: boolean
          description: The cached license is valid.
          example: true
        installation_id:
          type: string
          description: Installation identifier from the license server.
          example: inst_1234567890abcdef
        customer_id:
          type: string
          description: Customer identifier.
          example: cust_abcdef1234567890
        installation_name:
          type: string
          description: Installation name.
          example: Production Server
        plan_type:
          type: string
          description: Plan tier (`BASIC`, `PRO` or `ENTERPRISE`).
          example: ENTERPRISE
        plan_id:
          type: string
          description: Plan identifier; omitted when empty.
          example: plan_enterprise_monthly
        subscription_id:
          type: string
          description: Subscription identifier; omitted when empty.
          example: sub_0123456789abcdef
        expires_at:
          type: string
          format: date-time
          description: License expiration time.
          example: "2026-12-31T23:59:59Z"
        max_instances:
          type: integer
          description: Maximum instances allowed by the license.
          example: 10
        license_key:
          type: string
          description: Full license key, unmasked.
          example: ZUCKZAP-ENT-2026-EXAMPLE-KEY0-0000
        instance_id:
          type: string
          description: Identifier of this running gateway.
          example: zuckzapgo-prod-01
        last_validation:
          type: string
          format: date-time
          description: Time of the last successful validation.
          example: "2026-09-15T12:00:00Z"
        last_attempt:
          type: string
          format: date-time
          description: Time of the last validation attempt.
          example: "2026-09-15T12:00:00Z"
        days_until_expiry:
          type: integer
          description: Whole days until `expires_at` (negative or 0 when expired).
          example: 107
        is_in_grace_period:
          type: boolean
          description: The license is invalid but still inside its grace period.
          example: false
        grace_period_end:
          type: string
          format: date-time
          description: End of the grace period; the zero time outside it.
          example: "0001-01-01T00:00:00Z"
        status:
          type: string
          description: License status (the license server value wins when present).
          example: active
        status_message:
          type: string
          description: Human-readable status (currently Portuguese text).
          example: License active and valid
        features:
          type: array
          description: Feature names for the plan (currently Portuguese text).
          items:
            type: string
          example:
            - Complete RESTful API
            - Multiple instances
        support_email:
          type: string
          description: Support contact address.
          example: support@example.com
        documentation_url:
          type: string
          description: Documentation URL.
          example: https://github.com/guilhermejansen/use-zuckzapgo
        users_count:
          type: integer
          description: Users in the database (live count, cached value on failure).
          example: 4
        connected_users_count:
          type: integer
          description: Users flagged connected in the database (live count, cached value on failure).
          example: 3
        result:
          type: string
          description: Result code of the last validation response, or `status`.
          example: active
        message:
          type: string
          description: Message of the last validation response, or `status_message`.
          example: License active and valid
        http_status:
          type: integer
          description: HTTP status of the last validation call; omitted when 0.
          example: 200
        client_ip:
          type: string
          description: Public IP seen by the license server; omitted when empty.
          example: 203.0.113.10
        expected_ip:
          type: string
          description: IP expected by the license server; omitted when empty.
          example: 203.0.113.10
        allowed_ip_id:
          type: string
          description: Allowlist entry ID that matched; omitted when empty.
          example: ip_0123456789
        allowed_ip_address:
          type: string
          description: Allowlisted address that matched; omitted when empty.
          example: 203.0.113.10
        allowed_ip_label:
          type: string
          description: Label of the matching allowlist entry; omitted when empty.
          example: production
        allowlisted_ips:
          type: array
          description: IPs allowlisted for the license; omitted when empty.
          items:
            type: string
          example:
            - 203.0.113.10
        active_instances:
          type: array
          description: Gateways currently reporting under this license; omitted when empty.
          items:
            $ref: "#/components/schemas/AdminLicenseActiveInstance"
        last_error:
          type: string
          description: Last validation error; omitted when empty.
          example: license server timeout
        last_context:
          type: object
          additionalProperties: true
          description: Raw context of the last validation (timestamp, HTTP status, request and response); omitted when unavailable.
          example:
            timestamp: "2026-09-15T12:00:00Z"
            http_status: 200
    AdminLicenseActiveInstance:
      type: object
      description: One gateway reporting under the license.
      properties:
        instance_id:
          type: string
          description: Gateway instance identifier.
          example: zuckzapgo-prod-01
        container_id:
          type: string
          description: Container identifier; omitted when empty.
          example: 3f2a1b0c9d8e
        ip_address:
          type: string
          description: Reporting IP; omitted when empty.
          example: 203.0.113.10
        version:
          type: string
          description: Gateway version; omitted when empty.
          example: 1.20.4
        allowed_ip_id:
          type: string
          description: Allowlist entry used; omitted when empty.
          example: ip_0123456789
        last_seen:
          type: string
          format: date-time
          description: Last time the gateway reported.
          example: "2026-09-15T12:00:00Z"
    AdminGlobalStatsResponse:
      description: Success envelope of `GET /admin/global/stats`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/AdminGlobalDispatcherStats"
    AdminGlobalDispatcherStats:
      type: object
      description: Live snapshot of the global event dispatcher.
      properties:
        enabled:
          type: boolean
          description: The global dispatcher is enabled.
          example: true
        transportCount:
          type: integer
          description: Number of registered transports.
          example: 2
        transports:
          type: array
          description: Names of the registered transports.
          items:
            type: string
          example:
            - global-webhook
            - global-rabbitmq
        callEventIngress:
          type: object
          additionalProperties: true
          description: 'Call event outbox counters, or `{"enabled": false}`.'
          example:
            enabled: true
            accepting: true
            persisted: 120
            skipped: 0
            failures: 0
            rejected: 0
            durabilityBoundary: atomic_destination_ledger
        callRecordingFinalizer:
          type: object
          additionalProperties: true
          description: 'Call recording spool counters, or `{"enabled": false}`.'
          example:
            enabled: false
        webhook:
          type: object
          additionalProperties: true
          description: 'Global webhook configuration, concurrency slots, delivery metrics and `health`, or `{"enabled": false}`.'
          example:
            enabled: true
            url: https://hooks.example.com/global
            maxConcurrency: 50
            activeRequests: 1
            availableSlots: 49
        rabbitmq:
          type: object
          additionalProperties: true
          description: 'Global RabbitMQ configuration, pool and delivery metrics, circuit breaker state and `health`, or `{"enabled": false}`.'
          example:
            enabled: true
            connected: true
            circuitBreakerState: CLOSED
            queueDepth: 0
        sqs:
          type: object
          additionalProperties: true
          description: 'Global SQS statistics with `health`, or `{"enabled": false}`.'
          example:
            enabled: false
        redis:
          type: object
          additionalProperties: true
          description: 'Global Redis statistics with `health`, or `{"enabled": false}`.'
          example:
            enabled: false
        websocket:
          type: object
          additionalProperties: true
          description: 'Global WebSocket statistics with `health`, or `{"enabled": false}`.'
          example:
            enabled: false
        s3:
          $ref: "#/components/schemas/AdminGlobalS3Summary"
        buffer:
          $ref: "#/components/schemas/AdminGlobalBufferStats"
        dispatchRules:
          type: object
          additionalProperties: true
          description: Dispatch rule per transport name (enabled flag, allowAll and allowed events). Omitted when the rule cache is not initialized.
          example:
            global-webhook:
              enabled: true
              allowAll: true
              events:
                - All
        global_rabbitmq_queues:
          type: object
          additionalProperties: true
          description: Queues declared by the global RabbitMQ publisher. Omitted when it does not exist.
          example:
            dynamicQueues: false
            createdQueues: 1
            queueNames:
              - zuckzapgo.events
    AdminGlobalS3Summary:
      type: object
      description: Global S3 settings as seen by the dispatcher.
      properties:
        enabled:
          type: boolean
          description: Global S3 is enabled.
          example: true
        bucket:
          type: string
          description: Global bucket.
          example: zuckzapgo-media
        region:
          type: string
          description: Bucket region.
          example: us-east-1
        endpoint:
          type: string
          description: Custom endpoint; empty for AWS.
          example: ""
        mediaDelivery:
          type: string
          description: Global media delivery mode.
          example: both
        hasClient:
          type: boolean
          description: An S3 client exists and S3 is enabled.
          example: true
    AdminGlobalBufferStats:
      type: object
      description: 'Persistent event buffer counters, or `{"enabled": false}` without a buffer.'
      properties:
        enabled:
          type: boolean
          description: The persistent buffer is active.
          example: true
        pending:
          type: integer
          format: int64
          description: Events waiting for delivery.
          example: 4
        enqueued:
          type: integer
          format: int64
          description: Events written to the buffer since startup.
          example: 99755
        dequeued:
          type: integer
          format: int64
          description: Events taken by transport workers since startup.
          example: 99751
        ackSuccess:
          type: integer
          format: int64
          description: Deliveries acknowledged as successful.
          example: 99748
        ackFailures:
          type: integer
          format: int64
          description: Deliveries acknowledged as failed.
          example: 3
        requeued:
          type: integer
          format: int64
          description: Events put back for retry.
          example: 3
        replayed:
          type: integer
          format: int64
          description: Events re-enqueued from the DLQ or archive.
          example: 0
        archiveStored:
          type: integer
          format: int64
          description: Events written to the archive.
          example: 99748
        archivePruned:
          type: integer
          format: int64
          description: Archive rows removed by the pruner.
          example: 52000
    AdminGlobalTestResponse:
      description: Success envelope of `POST /admin/global/test`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Test outcome per transport.
              properties:
                webhook:
                  $ref: "#/components/schemas/AdminGlobalTransportTest"
                rabbitmq:
                  $ref: "#/components/schemas/AdminGlobalTransportTest"
                sqs:
                  $ref: "#/components/schemas/AdminGlobalTransportTest"
                redis:
                  $ref: "#/components/schemas/AdminGlobalTransportTest"
                websocket:
                  $ref: "#/components/schemas/AdminGlobalTransportTest"
                s3:
                  $ref: "#/components/schemas/AdminGlobalTransportTest"
                global_rabbitmq_health:
                  type: object
                  additionalProperties: true
                  description: Global RabbitMQ health (connection, pool, circuit breaker); omitted without the publisher.
                  example:
                    enabled: true
                    connected: true
                    circuitBreakerState: CLOSED
                global_rabbitmq_queue_stats:
                  type: object
                  additionalProperties: true
                  description: Queues declared by the global RabbitMQ publisher; omitted without the publisher.
                  example:
                    createdQueues: 1
    AdminGlobalTransportTest:
      type: object
      description: Result of one transport connectivity test.
      properties:
        enabled:
          type: boolean
          description: The transport is configured.
          example: true
        success:
          type: boolean
          description: The connectivity check passed.
          example: true
        error:
          type: string
          nullable: true
          description: Failure reason, or null on success.
          example: null
        bucket:
          type: string
          description: Only for `s3` when enabled.
          example: zuckzapgo-media
        region:
          type: string
          description: Only for `s3` when enabled.
          example: us-east-1
    AdminGlobalConfigResponse:
      description: Success envelope of `GET /admin/global/config`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Global configuration read from the environment.
              properties:
                webhook:
                  type: object
                  additionalProperties: true
                  description: "`GLOBAL_WEBHOOK_*` settings: enabled, url, events, timeout, retryCount, retryDelay, maxRetryDelay, backoffFactor, maxConcurrency."
                  example:
                    enabled: true
                    url: https://hooks.example.com/global
                    events:
                      - All
                    timeout: 30s
                rabbitmq:
                  type: object
                  additionalProperties: true
                  description: "`GLOBAL_RABBITMQ_*` settings: enabled, url (unmasked), events, exchange, exchangeType, queue, queueType, routingKey, durable, autoDelete, noWait, exclusive, deliveryMode."
                  example:
                    enabled: true
                    exchange: zuckzapgo.global
                    exchangeType: topic
                skip:
                  type: object
                  additionalProperties: true
                  description: "`GLOBAL_SKIP_*` and call rejection defaults."
                  example:
                    skipGroups: false
                    callRejectType: busy
                s3:
                  type: object
                  additionalProperties: true
                  description: "`GLOBAL_S3_*` settings; `secretKey` is masked, `accessKey` is not."
                  example:
                    enabled: true
                    bucket: zuckzapgo-media
                    secretKey: "****6789"
                environment_variables:
                  type: object
                  additionalProperties:
                    type: string
                  description: Supported environment variable names mapped to a short explanation.
                  example:
                    GLOBAL_WEBHOOK_URL: URL for global webhook endpoint
    AdminGlobalReloadResponse:
      description: Success envelope of `POST /admin/global/config/reload`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Reload result.
              properties:
                success:
                  type: boolean
                  description: Always `true`.
                  example: true
                message:
                  type: string
                  description: Confirmation text.
                  example: Global configurations reloaded successfully
                stats:
                  $ref: "#/components/schemas/AdminGlobalDispatcherStats"
    AdminS3RetentionStatsResponse:
      description: Success envelope of `GET /admin/global/s3-retention/stats`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Retention worker metrics.
              properties:
                enabled:
                  type: boolean
                  description: Retention is enabled in the configuration.
                  example: true
                running:
                  type: boolean
                  description: The worker loop is started.
                  example: true
                currently_running:
                  type: boolean
                  description: A cleanup cycle is in progress.
                  example: false
                last_run_time:
                  type: string
                  format: date-time
                  description: Start time of the last cycle (zero time before the first cycle).
                  example: "2026-09-15T10:30:00Z"
                last_run_duration:
                  type: string
                  description: Duration of the last cycle (Go duration string).
                  example: 2m30.5s
                objects_deleted:
                  type: integer
                  format: int64
                  description: Objects deleted since startup.
                  example: 15420
                bytes_reclaimed:
                  type: integer
                  format: int64
                  description: Bytes reclaimed since startup.
                  example: 1073741824
                error_count:
                  type: integer
                  format: int64
                  description: Errors since startup.
                  example: 2
                users_processed:
                  type: integer
                  format: int64
                  description: Users processed since startup.
                  example: 45
                cycle_count:
                  type: integer
                  format: int64
                  description: Completed cycles since startup.
                  example: 24
                config:
                  type: object
                  description: Worker configuration.
                  properties:
                    interval:
                      type: string
                      description: Time between cycles (Go duration string).
                      example: 1h0m0s
                    max_concurrency:
                      type: integer
                      description: Users processed in parallel.
                      example: 5
                    batch_size:
                      type: integer
                      description: Objects deleted per batch request.
                      example: 1000
                    objects_per_cycle:
                      type: integer
                      description: Maximum objects deleted per cycle.
                      example: 10000
    AdminS3RetentionTriggerResponse:
      description: Success envelope of `POST /admin/global/s3-retention/trigger`. There is no `data` key.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            message:
              type: string
              description: Confirmation that the cycle was requested.
              example: S3 retention cycle triggered. Check /admin/global/s3-retention/stats for progress.
    AdminGlobalEventTestResponse:
      description: Success envelope of `POST /admin/global/event/test`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Echo of the dispatched event.
              properties:
                success:
                  type: boolean
                  description: Always `true`.
                  example: true
                message:
                  type: string
                  description: Confirmation text.
                  example: Test event dispatched to global systems
                eventType:
                  type: string
                  description: Event type dispatched.
                  example: TestEvent
                userID:
                  type: string
                  description: User ID dispatched.
                  example: admin-test
                userToken:
                  type: string
                  description: Token dispatched.
                  example: admin-test
                data:
                  type: object
                  additionalProperties: true
                  description: Payload dispatched.
                  example:
                    test: true
    AdminMonitoringHistory:
      type: object
      description: Page of stored monitoring events (raw object, no envelope).
      properties:
        events:
          type: array
          description: Events ordered by creation time, newest first.
          items:
            $ref: "#/components/schemas/MonitoringEvent"
        has_more:
          type: boolean
          description: "`true` when the page is full; another page may exist."
          example: true
        next_id:
          type: integer
          format: int64
          description: ID of the last (oldest) event in the page when `has_more` is true; pass it as `before_id`. 0 otherwise.
          example: 4521
        count:
          type: integer
          description: Number of events in this page.
          example: 2
    AdminMonitoringPruneRequest:
      type: object
      description: Optional retention override for a manual monitoring prune.
      properties:
        retention_hours:
          type: integer
          description: Delete events older than this many hours. Values of 0 or less are ignored and the configured retention is used.
          example: 48
    AdminMonitoringPruneResult:
      type: object
      description: Result of a manual monitoring prune (raw object, no envelope).
      properties:
        deleted:
          type: integer
          format: int64
          description: Rows deleted from `monitoring_events`.
          example: 124
        retention_h:
          type: number
          description: Retention window applied, in hours.
          example: 48
        threshold:
          type: string
          format: date-time
          description: Events created before this time were deleted.
          example: "2026-09-13T12:00:00.000000001Z"
    AdminDLQFilterEcho:
      type: object
      description: Filter applied to a DLQ query, echoed with Go field names. Zero dates mean no date bound.
      properties:
        Transport:
          type: string
          description: Transport filter; empty when not set.
          example: global-webhook
        UserID:
          type: string
          description: User ID filter; empty when not set.
          example: ""
        EventType:
          type: string
          description: Event type filter; empty when not set.
          example: ""
        FromDate:
          type: string
          format: date-time
          description: Lower `failed_at` bound; `0001-01-01T00:00:00Z` when not set.
          example: "0001-01-01T00:00:00Z"
        ToDate:
          type: string
          format: date-time
          description: Upper `failed_at` bound; `0001-01-01T00:00:00Z` when not set.
          example: "0001-01-01T00:00:00Z"
        Limit:
          type: integer
          description: Effective limit.
          example: 50
        Offset:
          type: integer
          description: Effective offset.
          example: 0
    AdminDLQListResponse:
      description: Success envelope of `GET /admin/dlq`, with `count` and `filter` at the top level.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: array
              description: DLQ events, newest failure first.
              items:
                $ref: "#/components/schemas/DLQEvent"
            count:
              type: integer
              description: Number of events in `data`.
              example: 1
            filter:
              $ref: "#/components/schemas/AdminDLQFilterEcho"
    AdminDLQEventResponse:
      description: Success envelope of `GET /admin/dlq/{id}`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/DLQEvent"
    AdminDLQStatsResponse:
      description: Success envelope of `GET /admin/dlq/stats`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/DLQStats"
    AdminDLQReplayRequest:
      type: object
      description: Single replay (`event_id`) or bulk replay (filters). All fields are optional.
      properties:
        event_id:
          type: integer
          format: int64
          description: DLQ event ID to replay. When present, the filters and `limit` are ignored.
          example: 1234
        dry_run:
          type: boolean
          default: false
          description: Preview only; nothing is re-enqueued.
          example: false
        transport:
          type: string
          description: Bulk filter by transport.
          example: global-webhook
        user_id:
          type: string
          description: Bulk filter by user ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Bulk filter by event type.
          example: Message
        limit:
          type: integer
          default: 50
          maximum: 1000
          description: Maximum events replayed in bulk mode (0 uses 50, values above 1000 are capped).
          example: 200
    AdminDLQReplayResponse:
      description: Success envelope of `POST /admin/dlq/replay`. There is no `data` key; the fields present depend on the mode.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            replayed:
              type: integer
              description: Events re-enqueued (0 for dry runs and empty matches).
              example: 1
            event_id:
              type: integer
              format: int64
              description: Single mode, real replay only.
              example: 1234
            message:
              type: string
              description: Outcome text (single mode, dry runs and empty matches).
              example: Event replayed successfully
            dry_run:
              type: boolean
              description: Present and `true` for previews.
              example: true
            event:
              $ref: "#/components/schemas/DLQEvent"
            events:
              type: array
              description: Bulk dry run only; the events that would be replayed.
              items:
                $ref: "#/components/schemas/DLQEvent"
            count:
              type: integer
              description: Bulk dry run only; number of events listed.
              example: 42
            failed:
              type: integer
              description: Bulk replay only; events that could not be replayed.
              example: 0
            total:
              type: integer
              description: Bulk replay only; events attempted.
              example: 42
            errors:
              type: array
              description: Bulk replay only; one message per failed event. Omitted without failures.
              items:
                type: string
              example:
                - "Event ID 1201: failed to unmarshal envelope: unexpected end of JSON input"
            filter:
              $ref: "#/components/schemas/AdminDLQFilterEcho"
    AdminDLQBulkDeleteRequest:
      type: object
      description: Filters for bulk DLQ deletion. At least one of `transport`, `user_id` or `event_type` is required.
      properties:
        transport:
          type: string
          description: Delete events that failed on this transport.
          example: global-webhook
        user_id:
          type: string
          description: Delete events of this user ID.
          example: bec45bb93cbd24cbec32941ec3c93a12
        event_type:
          type: string
          description: Delete events of this event type.
          example: Receipt
        limit:
          type: integer
          default: 50
          maximum: 1000
          description: Maximum events deleted in this call (0 uses 50, values above 1000 are capped).
          example: 1000
    AdminDLQDeleteResponse:
      description: Success envelope of `DELETE /admin/dlq/{id}` and `DELETE /admin/dlq`. There is no `data` key; the fields present depend on the mode.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            deleted:
              type: integer
              description: Events deleted.
              example: 1
            event_id:
              type: integer
              format: int64
              description: Single deletion only.
              example: 1234
            message:
              type: string
              description: Outcome text (single deletion and empty matches).
              example: Event deleted successfully
            failed:
              type: integer
              description: Bulk deletion only; events that could not be deleted.
              example: 0
            total:
              type: integer
              description: Bulk deletion only; events attempted.
              example: 42
            errors:
              type: array
              description: Bulk deletion only; one message per failed event. Omitted without failures.
              items:
                type: string
              example:
                - "Event ID 1201: DLQ event not found"
            filter:
              $ref: "#/components/schemas/AdminDLQFilterEcho"
    AdminDLQPruneResponse:
      description: Success envelope of `POST /admin/dlq/prune`. There is no `data` key.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            message:
              type: string
              description: Confirmation text.
              example: prune triggered successfully
            pruner_stats:
              $ref: "#/components/schemas/AdminBufferPrunerStats"
    AdminBufferPrunerStats:
      type: object
      description: Persistent buffer pruner state after the run. Omitted when the buffer does not expose it.
      properties:
        running:
          type: boolean
          description: The scheduled pruner is running.
          example: true
        retention_days:
          type: integer
          description: Retention of completed buffer rows, in days.
          example: 7
        archive_retention:
          type: integer
          description: Retention of archive rows, in days.
          example: 30
        total_pruned:
          type: integer
          format: int64
          description: Buffer rows pruned since startup.
          example: 182340
        total_archive_pruned:
          type: integer
          format: int64
          description: Archive rows pruned since startup.
          example: 52000
        last_prune_time:
          type: string
          format: date-time
          description: Time of the last prune.
          example: "2026-09-15T12:00:03Z"
        last_error:
          type: string
          description: Error of the last prune; omitted when it succeeded.
          example: "failed to prune archive: context deadline exceeded"
        next_prune_time:
          type: string
          format: date-time
          description: Time of the next scheduled prune.
          example: "2026-09-16T00:00:00Z"
    AdminArchiveListResponse:
      description: Success envelope of `GET /admin/archive`, with `count` and `filter` at the top level.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              type: array
              description: Archived events, newest first.
              items:
                $ref: "#/components/schemas/ArchiveEvent"
            count:
              type: integer
              description: Number of rows in `data`.
              example: 2
            filter:
              $ref: "#/components/schemas/ArchiveFilter"
    AdminArchiveEventResponse:
      description: Success envelope of `GET /admin/archive/{id}`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/ArchiveEvent"
    AdminArchiveStatsResponse:
      description: Success envelope of `GET /admin/archive/stats`.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/ArchiveStats"
    AdminArchiveReplayResponse:
      description: Success envelope of `POST /admin/archive/replay`. There is no `data` key; the fields present depend on the mode.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            replayed:
              type: integer
              description: Rows re-enqueued (0 for dry runs and empty matches).
              example: 1
            event_id:
              type: integer
              format: int64
              description: Single mode, real replay only.
              example: 99810
            message:
              type: string
              description: Outcome text (single mode, dry runs and empty matches).
              example: Event replayed successfully
            dry_run:
              type: boolean
              description: Present and `true` for previews.
              example: true
            event:
              $ref: "#/components/schemas/ArchiveEvent"
            events:
              type: array
              description: Bulk dry run only; the rows that would be replayed.
              items:
                $ref: "#/components/schemas/ArchiveEvent"
            count:
              type: integer
              description: Bulk dry run only; number of rows listed.
              example: 0
            failed:
              type: integer
              description: Bulk replay only; rows that could not be replayed.
              example: 0
            total:
              type: integer
              description: Bulk replay only; rows attempted.
              example: 312
            errors:
              type: array
              description: Bulk replay only; one message per failed row. Omitted without failures.
              items:
                type: string
              example:
                - "Event ID 99001: failed to unmarshal event payload: unexpected end of JSON input"
            filter:
              $ref: "#/components/schemas/ArchiveFilter"
    AdminArchiveDeleteResponse:
      description: Success envelope of `DELETE /admin/archive/{id}` and `DELETE /admin/archive`. There is no `data` key; the fields present depend on the mode.
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"
        - type: object
          properties:
            deleted:
              type: integer
              description: Rows deleted.
              example: 1
            event_id:
              type: integer
              format: int64
              description: Single deletion only.
              example: 99810
            message:
              type: string
              description: Outcome text (single deletion and empty matches).
              example: Event deleted successfully
            failed:
              type: integer
              description: Bulk deletion only; rows that could not be deleted.
              example: 0
            total:
              type: integer
              description: Bulk deletion only; rows attempted.
              example: 1000
            errors:
              type: array
              description: Bulk deletion only; one message per failed row. Omitted without failures.
              items:
                type: string
              example:
                - "Event ID 99001: archive event not found"
            filter:
              $ref: "#/components/schemas/ArchiveFilter"
    ErrorEnvelope:
      type: object
      description: Error envelope written by the gateway for failed requests.
      required:
        - code
        - error
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 400
        error:
          type: string
          description: Human-readable error message.
          example: missing Phone in Payload
        success:
          type: boolean
          description: Always false for errors.
          example: false
    SendMessageResult:
      type: object
      description: Result of a successful send.
      properties:
        Details:
          type: string
          description: Always `Sent` for regular sends.
          example: Sent
        Id:
          type: string
          description: WhatsApp message ID (the custom `Id` when provided, otherwise generated).
          example: 3EB0C767D26A1D6B8A9F
        Timestamp:
          type: string
          description: Server acknowledgement time.
          format: date-time
          example: "2026-09-15T10:30:00-03:00"
    BusinessOrderRecoveryBody:
      type: object
      x-raw-error-body: true
      description: Recovery body written WITHOUT the gateway envelope (no `code`) when an order was registered but the message could not be built or delivered. Keep `registered_order` and never retry as a new order.
      properties:
        success:
          type: boolean
          description: Always false.
          example: false
        order_created:
          type: boolean
          description: True when this request registered the order.
          example: true
        order_reused:
          type: boolean
          description: True when an existing order was being resent.
          example: false
        order_exists:
          type: boolean
          description: True when a registered order with ID and token exists.
          example: true
        message_sent:
          type: boolean
          description: False when no send was attempted; null when delivery is unknown.
          nullable: true
        message_delivery:
          type: string
          description: "`not_attempted` or `unknown`."
          enum:
            - unknown
            - not_attempted
          example: unknown
        error:
          type: string
          description: Failure phase.
          enum:
            - order_creation_failed
            - order_created_message_build_failed
            - order_created_message_send_failed
          example: order_created_message_send_failed
        registered_order:
          description: Server-issued order to recover with `resend_registered_order`.
          allOf:
            - $ref: "#/components/schemas/whatsmeow.BusinessCreatedOrder"
    BusinessOrderSendResult:
      type: object
      description: Order message result.
      properties:
        success:
          type: boolean
          description: Always true on success.
          example: true
        message:
          type: string
          description: Fixed confirmation text.
          example: Order sent successfully
        business_jid:
          type: string
          description: Seller chat the order was sent to.
          example: 5511988888888@s.whatsapp.net
        message_id:
          type: string
          description: WhatsApp message ID.
          example: 3EB0C767D26A1D6B8A9F
        timestamp:
          type: string
          description: Server acknowledgement time.
          format: date-time
          example: "2026-09-15T12:00:00Z"
        order_created:
          type: boolean
          description: True only when this request registered a new order (`create_order`).
          example: true
        order_reused:
          type: boolean
          description: True when an existing registered order was resent.
          example: false
        order_kind:
          type: string
          description: "`legacy_summary` or `registered_order`."
          enum:
            - legacy_summary
            - registered_order
          example: registered_order
        registered_order:
          type: object
          description: Server-issued order (registered orders only).
          properties:
            order_id:
              type: string
              description: Order ID.
              example: "1976123456789012"
            token:
              type: string
              description: Order token; keep it to recover or resend the order.
              example: AR6example0rderT0kenBase64==
            price:
              type: object
              description: Server-issued totals.
              properties:
                subtotal_amount:
                  type: string
                  description: Subtotal in thousandths.
                  example: "3000000"
                total_amount:
                  type: string
                  description: Total in thousandths.
                  example: "3000000"
                currency:
                  type: string
                  description: Currency.
                  example: BRL
                price_status:
                  type: string
                  description: Price status.
                  example: PROVIDED
        order_details:
          type: object
          description: Summary of what was sent.
          properties:
            item_count:
              type: integer
              description: Number of order lines sent.
              example: 1
            order_id:
              type: string
              description: Order ID (empty for legacy summaries without `order_id`).
              example: "1976123456789012"
    BusinessLegacyProduct:
      type: object
      description: Product in the compact shape used by `GET /business/catalog/{businessJID}` and `GET /business/products/{businessJID}`.
      properties:
        id:
          type: string
          description: Catalog product ID.
          example: "7123456789012345"
        retailer_id:
          type: string
          description: Retailer ID, when set.
          example: CHAIR-001
        name:
          type: string
          description: Product name.
          example: Wooden chair
        description:
          type: string
          description: Product description, when set.
          example: Solid wood chair with natural finish.
        price:
          type: integer
          nullable: true
          description: Price as a JSON number holding the upstream thousandths value unchanged (`3000000` = 3,000.00); `null` when the upstream product has no usable price.
          example: 3000000
        currency:
          type: string
          description: ISO-4217 currency.
          example: BRL
        image_urls:
          type: array
          description: Image URLs (original, or resized when no original is available).
          items:
            type: string
          example:
            - https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
        availability:
          type: string
          description: Availability label, when reported.
          example: in stock
    BusinessEmptyRequest:
      type: object
      additionalProperties: false
      description: Empty JSON object. No properties are accepted.
      example: {}
    BusinessCollectionMutationResult:
      type: object
      description: Identifier and review status of a created or edited collection.
      properties:
        id:
          type: string
          description: Collection ID. Edits return a NEW ID; use it from then on.
          example: "7123456789012350"
        review_status:
          type: string
          description: "Meta review status as returned by the server (observed: `STATUS_APPROVED`)."
          example: STATUS_APPROVED
      example:
        id: "7123456789012350"
        review_status: STATUS_APPROVED
    BusinessCatalogProduct:
      type: object
      description: Catalog product as returned by WhatsApp (typed business routes). Prices are strings holding integer thousandths of the currency.
      properties:
        id:
          type: string
          description: Catalog product ID.
          example: "7123456789012345"
        retailer_id:
          type: string
          description: Seller SKU / retailer ID, when set.
          example: CHAIR-001
        belongs_to:
          type: string
          description: Owner reference reported by the server, when present.
          example: 5511988888888@s.whatsapp.net
        name:
          type: string
          description: Product name.
          example: Wooden chair
        description:
          type: string
          description: Product description.
          example: Solid wood chair with natural finish.
        price:
          type: string
          description: Price in thousandths of the currency (`"3000000"` = 3,000.00).
          example: "3000000"
        currency:
          type: string
          description: ISO-4217 currency code.
          example: BRL
        url:
          type: string
          description: Product web page. The server normalizes it with a trailing slash.
          example: https://example.com/products/chair/
        shimmed_url:
          type: string
          description: Tracking-safe redirect URL generated by WhatsApp, when present.
          example: https://l.wl.co/l?u=https%3A%2F%2Fexample.com%2Fproducts%2Fchair%2F
        is_hidden:
          type: boolean
          description: True when the product is hidden from public catalog reads.
          example: false
        is_sanctioned:
          type: boolean
          description: True when Meta restricted the product.
          example: false
        max_available:
          type: integer
          description: Maximum orderable quantity, when reported.
          example: 10
        product_availability:
          type: string
          description: Availability label reported by the server.
          example: in stock
        compliance_category:
          type: string
          description: Compliance category, e.g. `COUNTRY_ORIGIN_EXEMPT`.
          example: COUNTRY_ORIGIN_EXEMPT
        compliance_info:
          description: Country of origin and importer data, when set.
          allOf:
            - $ref: "#/components/schemas/types.BusinessComplianceInfo"
        media:
          type: object
          description: Product photos and videos.
          properties:
            images:
              type: array
              description: Photos; the first one is the main photo.
              items:
                type: object
                properties:
                  id:
                    type: string
                    description: Media ID.
                    example: "7123456789012346"
                  original_image_url:
                    type: string
                    description: Full-size image URL.
                    example: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
                  request_image_url:
                    type: string
                    description: Resized image URL (width/height of the request).
                    example: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
            videos:
              type: array
              description: Videos, when present.
              items:
                type: object
                properties:
                  id:
                    type: string
                    description: Media ID.
                    example: "7123456789012349"
                  original_video_url:
                    type: string
                    description: Video URL.
                    example: https://mmg.whatsapp.net/v/t42.1790-2/example-video.mp4
                  thumbnail_url:
                    type: string
                    description: Video thumbnail URL.
                    example: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
        sale_price:
          type: object
          description: Sale price, when set.
          properties:
            price:
              type: string
              description: Sale price in thousandths of the currency.
              example: "2499900"
            start_date:
              type: string
              description: Sale start, when scheduled.
              example: "2026-09-01"
            end_date:
              type: string
              description: Sale end, when scheduled.
              example: "2026-09-30"
        status_info:
          type: object
          description: Meta review state.
          properties:
            status:
              type: string
              description: "Review status: `NO_REVIEW`, `PENDING`, `APPROVED` or `REJECTED`."
              example: APPROVED
            can_appeal:
              type: boolean
              description: True when a rejected product can be appealed.
              example: false
        variant_info:
          type: object
          additionalProperties: true
          description: Variant availability, listing details, types and properties, when the product has variants.
          example:
            types:
              - name: Color
                options:
                  - value: Natural
      example:
        id: "7123456789012345"
        retailer_id: CHAIR-001
        name: Wooden chair
        description: Solid wood chair with natural finish.
        price: "3000000"
        currency: BRL
        url: https://example.com/products/chair/
        is_hidden: false
        is_sanctioned: false
        product_availability: in stock
        compliance_info:
          country_code_origin: BR
        media:
          images:
            - id: "7123456789012346"
              original_image_url: https://mmg.whatsapp.net/v/t45.5328-4/example-chair.jpg?ccb=11-4&oh=01_Q5AaIexample&oe=66E6B2C0
        sale_price:
          price: "2499900"
        status_info:
          status: APPROVED
    SuccessEnvelope:
      type: object
      description: "Success envelope written by the gateway (`s.Respond`) for 2xx responses. Compose it with `allOf` and override `data` with the operation-specific payload schema."
      required:
        - code
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 200
        data:
          description: Operation-specific result. JSON strings produced by the handler are decoded into objects or arrays before being written.
        success:
          type: boolean
          description: Always true for 2xx responses.
          example: true
    HealthStatusEnvelope:
      description: "Health report written with a non-2xx status (`503` when a dependency is unhealthy). The body has no `error` key: `data` is the legacy string `ERROR`, `success` is false and `status` carries the overall state, alongside the regular health fields."
      x-status-envelope: true
      allOf:
        - $ref: "#/components/schemas/main.HealthCheckResponse"
      example:
        code: 503
        data: ERROR
        success: false
        status: unhealthy
        version: 1.20.4
        uptime: 86400
        timestamp: "2026-09-15T12:00:00Z"
        services:
          database:
            status: down
            error: database not configured
    LegacyErrorEnvelope:
      type: object
      description: "Legacy error envelope kept for backward compatibility: some handlers write the error in `data` (a string, or an object carrying `message` or `error`) and report `success: true` even though the HTTP status is an error. Clients must check the HTTP status code."
      x-legacy-envelope: true
      required:
        - code
        - data
        - success
      properties:
        code:
          type: integer
          description: HTTP status code repeated in the body.
          example: 404
        data:
          description: "Error written by the handler: a plain message string or an object carrying `message` or `error`."
          oneOf:
            - type: string
              description: Error message.
              example: Client not found
            - type: object
              description: Error object (for example newsletter, passkey and group history handlers). Carries `message` or `error`; other keys depend on the route.
              additionalProperties: true
              properties:
                success:
                  type: boolean
                  description: False inside the error object, when present.
                  example: false
                message:
                  type: string
                  description: Error message, when the handler uses `message`.
                  example: newsletter not found
                error:
                  type: string
                  description: Error code or message, when the handler uses `error`.
                  example: not_connected
                reason:
                  type: string
                  description: Human-readable reason, when present.
                  example: session is not connected
          example: Client not found
        success:
          type: boolean
          description: "Legacy: true even on error for these routes. Use the HTTP status code instead."
          example: true
    main.HealthCheckCapabilities:
      type: object
      properties:
        webhook_fallback:
          type: integer
          example: 1

security:
  - ApiKeyAuth: []
