# Wapito public API contract. Source of truth for the docs site, the api contract test,
# the generated SDK snippets and the pSEO reference pages. Lint with: npm run lint
openapi: 3.1.0
info:
  title: Wapito API
  version: 1.0.0
  summary: >-
    Unofficial WhatsApp REST API with webhooks: messages, groups, Channels, statuses, contacts and labels.
  description: |-
    The Wapito API turns any WhatsApp number into a REST API with webhooks. Link a number by QR code or
    pairing code, get a channel token, and send and receive messages, manage groups, WhatsApp Channels,
    statuses, contacts, presence and Business labels over HTTP.

    ## Honesty about what this is

    Wapito is an **unofficial** WhatsApp API. It drives a real WhatsApp account through the same protocol the
    mobile and desktop apps use, rather than through Meta's Cloud API. That is what makes group creation,
    Channel posting, status updates and template-free messaging possible at all — none of which the official
    API allows. It also means **WhatsApp can ban the linked number**, and no provider, Wapito included, can
    promise otherwise. Every operation in this specification carries an `x-wapito.ban_risk` rating, the
    platform spaces and jitters your sends, enforces a warm-up ladder on new numbers and caps cold outreach,
    and the `channel` webhook event tells you the moment a number is lost. Use a number you can afford to
    lose, message people who expect to hear from you, and keep a human in the loop.

    ## Using the API

    Authenticate with `Authorization: Bearer wpt_…`. The token is bound to one channel, so no request carries
    a channel id. Recipients may be given as `+E.164`, bare digits, `@s.whatsapp.net`, `@c.us`, `@g.us`,
    `@lid` or `@newsletter`; ids in responses are always normalised to `@s.whatsapp.net`. Errors share one
    envelope, `{ "error": { "code", "message", "details?", "request_id" } }`, and you should branch on `code`.

    ## Engines

    Every channel runs on one of two protocol engines, `gows` (default) or `noweb`. A handful of operations
    exist on only one of them and answer `501 engine_unsupported_feature` with a hint on the other; the
    per-operation `x-wapito.engines` block and `feature-matrix.yaml` record the full matrix. Operations marked
    `x-phase: 2` are published for contract stability and answer `501 feature_not_available` today.
  termsOfService: https://wapito.com/legal/terms/
  contact:
    name: Wapito Support
    url: https://wapito.com/support/
    email: support@wapito.com
  license:
    name: Wapito API Terms of Service
    url: https://wapito.com/legal/terms/
servers:
  - url: https://api.wapito.com/v1
    description: >-
      Production. There is no separate sandbox host: a sandbox channel uses the same base URL with lower
      limits.
security:
  - bearerAuth: []
tags:
  - name: channel
    description: >-
      The channel itself: connection status, health, quotas, settings, QR and pairing-code linking, the
      profile of the linked account, incoming calls and metered usage. Everything here is scoped to the single
      channel your token belongs to.
  - name: messages
    description: >-
      Send text, media, links, locations, contact cards and polls; read, edit, delete, forward, star and react
      to messages. Sends pass through the per-channel queue that spaces and jitters traffic to keep the number
      healthy.
  - name: chats
    description: >-
      The conversation list: read chats, mark them read or unread, archive them and delete them locally. Chat
      data comes from the engine store, so it reflects what the engine has observed since the channel
      connected.
  - name: contacts
    description: >-
      Contacts known to the linked account, checking whether a number is registered on WhatsApp, and the
      phone-number to LID mapping that modern WhatsApp traffic increasingly depends on.
  - name: presence
    description: >-
      Publish online, typing and recording indicators, subscribe to presence for a chat and read the last
      state observed. WhatsApp only streams presence for chats you explicitly subscribe to.
  - name: groups
    description: >-
      Create groups, manage participants and admins, control the invite link and the four security settings,
      work the join-request queue and leave. Creating groups and adding participants are the highest ban-risk
      calls in the API.
  - name: communities
    description: >-
      WhatsApp Communities, which group several groups behind one announcement group. The whole surface is
      phase 2 and answers `501 feature_not_available`: no supported engine implements the community protocol
      yet.
  - name: newsletters
    description: >-
      WhatsApp Channels, called newsletters at protocol level: create one, list and search them, read their
      posts and delete them. Posting is an ordinary send to a `@newsletter` recipient.
  - name: stories
    description: >-
      Status updates: post text, image, video and voice statuses to selected contacts. Statuses expire after
      twenty-four hours and are only visible to people who have the number saved.
  - name: labels
    x-displayName: Labels & Business
    description: >-
      WhatsApp Business labels and their chat associations, plus the phase-2 business profile, catalog and
      orders surface. Labels require a WhatsApp Business account; personal numbers get
      `business_account_required`.
  - name: media
    description: >-
      Upload files once and reuse them across sends, download the files Wapito copied out of inbound messages,
      and delete stored files. Signed links let you hand media to a browser without exposing your channel
      token.
  - name: webhooks
    description: >-
      Manage webhook subscriptions and test them, and the reference for every event Wapito delivers.
      Deliveries are signed with HMAC-SHA256, retried with backoff and documented under the `webhooks` section
      of this specification.
externalDocs:
  description: Guides, tutorials and the rendered reference
  url: https://wapito.com/docs/
paths:
  /channel:
    get:
      operationId: getChannel
      tags:
        - channel
      summary: Get channel status and settings
      description: >-
        Returns the live state of the channel your token belongs to: the linked phone number, the protocol
        engine running it, the plan in force, the current connection status and the behaviour settings applied
        to every send. Poll this after linking a number to watch the status move from `qr` or `pairing` to
        `connected`, or subscribe to the `channel` webhook event and stop polling entirely.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks: []
        errors:
          - channel_locked
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: Current channel state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelInfo"
              example:
                id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
                name: Acme Support
                engine: gows
                plan: premium
                status: connected
                phone: "15557654321"
                lid: 992814736450921@lid
                push_name: Acme Support
                connected_at: "2026-09-04T10:22:31.000Z"
                last_seen_at: "2026-09-15T07:41:08.000Z"
                created_at: "2026-09-04T10:19:02.000Z"
                webhooks_count: 2
                settings:
                  send_delay_ms: 1500
                  typing_simulation: auto
                  include_raw: false
                  auto_read: false
                  reject_calls: true
                  proxy: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
  /channel/health:
    get:
      operationId: getChannelHealth
      tags:
        - channel
      summary: Check channel and engine health
      description: >-
        Pings the protocol engine behind the channel and reports how long the session has been up, when
        WhatsApp was last seen, how many sends and webhook deliveries are still queued, and the result of each
        individual probe. This is the endpoint to wire into an uptime monitor: it fails fast with
        `engine_unavailable` when the engine is down instead of hanging.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks: []
        errors:
          - engine_unavailable
          - engine_timeout
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: Health snapshot for the channel.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelHealth"
              example:
                status: connected
                engine: gows
                session: WORKING
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /channel/limits:
    get:
      operationId: getChannelLimits
      tags:
        - channel
      summary: Get quotas, warm-up and timelock state
      description: >-
        Returns everything that can throttle this channel right now: plan quotas with their current usage and
        reset times, the warm-up ladder a freshly linked number is climbing, the hourly cold-send guard, the
        WhatsApp reachout timelock and the engine capping counters. Read it before a bulk run so you size the
        batch to the remaining allowance instead of discovering the limit through 429 responses.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks: []
        errors:
          - channel_locked
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: Every limit that currently applies.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelLimits"
              example:
                plan: premium
                rate_limit_per_minute: 300
                media_max_bytes: 67108864
                webhooks_max: 5
                sent:
                  used: 412
                  cap: null
                  resets_at: "2026-09-16T00:00:00.000Z"
                number_checks:
                  used: 38
                  cap: 2000
                  resets_at: "2026-09-16T00:00:00.000Z"
                api_requests:
                  used: 19422
                  cap: null
                  resets_at: "2026-10-01T00:00:00.000Z"
                warmup:
                  day: 11
                  cap: null
                  ladder_complete: true
                cold_send:
                  window_cap: 60
                  used: 4
                  resets_at: "2026-09-15T09:00:00.000Z"
                timelock:
                  active: false
                  until: null
                  reason: null
                capping:
                  unlimited: false
                  usedQuota: 12
                  totalQuota: 250
                  remaining: 238
                  reset_at: 2026-09-16T00:00:00.000Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /channel/settings:
    patch:
      operationId: updateChannelSettings
      tags:
        - channel
      summary: Update channel settings
      description: >-
        Changes the behaviour flags applied to every request on this channel: the spacing between queued
        sends, typing simulation, automatic read receipts, automatic call rejection, whether raw engine
        payloads are attached to webhook events, and the outbound proxy. Only the fields you send are changed.
        Setting a proxy requires a premium channel and restarts the engine session, which briefly disconnects
        the number.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks:
          - channel
        errors:
          - invalid_request
          - plan_required
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelSettingsRequest"
            example:
              send_delay_ms: 2500
              typing_simulation: auto
              reject_calls: true
      responses:
        "200":
          description: Settings after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelInfo"
              example:
                id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
                name: Acme Support
                engine: gows
                plan: premium
                status: connected
                phone: "15557654321"
                lid: 992814736450921@lid
                push_name: Acme Support
                connected_at: "2026-09-04T10:22:31.000Z"
                last_seen_at: "2026-09-15T07:41:08.000Z"
                created_at: "2026-09-04T10:19:02.000Z"
                webhooks_count: 2
                settings:
                  send_delay_ms: 1500
                  typing_simulation: auto
                  include_raw: false
                  auto_read: false
                  reject_calls: true
                  proxy: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /channel/qr:
    get:
      operationId: getChannelQr
      tags:
        - channel
      summary: Get the pairing QR code
      description: >-
        Returns a PNG QR code to scan from WhatsApp on the phone you want to link, as base64 bytes plus the
        number of seconds before WhatsApp rotates it. The code is only available while the channel is in the
        `qr` state; asking for it while the session is still booting returns `channel_not_in_qr_state`, so
        retry for a few seconds rather than failing the flow.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks:
          - channel
        errors:
          - channel_not_in_qr_state
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: A QR code that is valid for a few seconds.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QrCode"
              example:
                data: iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAA…
                format: image/png
                expires_in: 20
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /channel/pairing-code:
    post:
      operationId: createPairingCode
      tags:
        - channel
      summary: Request a pairing code for a number
      description: >-
        Asks WhatsApp for an eight-character pairing code so the user can link their number by typing a code
        into WhatsApp instead of scanning a QR. Pass the number being linked; the code is shown once and
        expires in about two minutes. Pairing codes are the better choice in a hosted onboarding flow because
        the user never has to point a camera at your screen.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks:
          - channel
        errors:
          - invalid_request
          - channel_not_in_qr_state
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PairingCodeRequest"
            example:
              phone: "+15557654321"
      responses:
        "201":
          description: A pairing code to type into WhatsApp on the phone.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PairingCode"
              example:
                code: J7K2-QW31
                expires_in: 120
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /channel/logout:
    post:
      operationId: logoutChannel
      tags:
        - channel
      summary: Log the number out of the channel
      description: >-
        Unlinks the WhatsApp number from the channel and stops the engine session. The channel itself and its
        token survive, so you can link a different number afterwards without issuing new credentials. Message
        history held by the engine is discarded, and the warm-up ladder resets when a different number is
        linked next. Existing webhooks keep their configuration.
      x-wapito:
        feature: connect-number-qr-pairing
        ban_risk: low
        webhooks:
          - channel
        errors:
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: The number was unlinked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /users/profile:
    get:
      operationId: getProfile
      tags:
        - channel
      summary: Get the linked account profile
      description: >-
        Returns the public profile of the linked WhatsApp account itself: the display name other users see,
        the about text and the profile picture URL. Use it to show the connected identity in your own
        dashboard so operators can tell at a glance which number a channel is driving, and to confirm a
        profile change actually reached WhatsApp.
      x-wapito:
        feature: profile
        ban_risk: low
        webhooks: []
        errors:
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: Profile of the linked account.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
              example:
                id: 15557654321@s.whatsapp.net
                phone: "15557654321"
                lid: 992814736450921@lid
                name: Acme Support
                push_name: Acme Support
                short_name: Acme
                is_business: true
                is_me: true
                is_blocked: false
                picture: https://pps.whatsapp.net/v/t61.24694-24/me_n.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: updateProfile
      tags:
        - channel
      summary: Update the linked account profile
      description: >-
        Changes the display name, the about text or the profile picture of the linked WhatsApp account. Send
        `picture: null` to remove the current photo. A recognisable business name and picture measurably
        reduce the rate at which recipients report an unknown number as spam, so set them before your first
        outbound campaign rather than after it.
      x-wapito:
        feature: profile
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - channel_not_connected
          - unsupported_media_type
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProfileRequest"
            example:
              name: Acme Support
              status: Replies Mon-Fri, 9 to 6 UK time.
      responses:
        "200":
          description: Profile after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
              example:
                id: 15557654321@s.whatsapp.net
                phone: "15557654321"
                lid: 992814736450921@lid
                name: Acme Support
                push_name: Acme Support
                short_name: Acme
                is_business: true
                is_me: true
                is_blocked: false
                picture: https://pps.whatsapp.net/v/t61.24694-24/me_n.jpg
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /calls/reject:
    post:
      operationId: rejectCall
      tags:
        - channel
      summary: Reject an incoming WhatsApp call
      description: >-
        Rejects a call that is currently ringing on the linked number, using the call id and caller delivered
        by the `calls` webhook event. Wapito cannot answer calls, only decline them. If you never want to
        handle calls at all, set `reject_calls` in the channel settings and every incoming call is declined
        automatically without a round trip through your code.
      x-wapito:
        feature: calls
        ban_risk: low
        webhooks:
          - calls
        errors:
          - invalid_request
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RejectCallRequest"
            example:
              call_id: CALL_9F31A0C4D7E2B6081A55
              from: "+15551234567"
      responses:
        "200":
          description: The call was rejected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: CALL_9F31A0C4D7E2B6081A55
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /usage:
    get:
      operationId: getUsage
      tags:
        - channel
      summary: Get metered usage for a date range
      description: >-
        Returns the five counters Wapito meters for this channel, both as a total and broken down per day:
        messages sent, messages received, billable API requests, numbers checked and webhook deliveries
        attempted. The range defaults to the current calendar month. Counters are written as requests
        complete, so the current day keeps moving while you read it.
      x-wapito:
        feature: usage
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: from
          in: query
          required: false
          description: First day to include, `YYYY-MM-DD`. Defaults to the first day of the current month.
          schema:
            type: string
            format: date
            examples:
              - "2026-09-01"
        - name: to
          in: query
          required: false
          description: Last day to include, `YYYY-MM-DD`. Defaults to today.
          schema:
            type: string
            format: date
            examples:
              - "2026-09-15"
      responses:
        "200":
          description: Usage over the requested range.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Usage"
              example:
                from: "2026-09-01"
                to: "2026-09-15"
                totals:
                  sent: 5821
                  received: 9104
                  api_requests: 19422
                  number_checks: 412
                  webhook_deliveries: 14903
                days:
                  - date: 2026-09-14
                    sent: 401
                    received: 712
                    api_requests: 1388
                    number_checks: 26
                    webhook_deliveries: 1102
                  - date: 2026-09-15
                    sent: 412
                    received: 688
                    api_requests: 1290
                    number_checks: 38
                    webhook_deliveries: 1044
                limits:
                  sent_per_day: null
                  number_checks_per_day: null
                  api_requests_per_month: null
                month:
                  api_requests: 19422
                  cap: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
  /messages/text:
    post:
      operationId: sendText
      tags:
        - messages
      summary: Send a text message
      description: >-
        Sends a plain text message to a person, a group or a WhatsApp Channel, with no Meta message template
        and no 24-hour window. WhatsApp markdown is passed through unchanged, `quoted` turns the message into
        a reply and `mentions` makes `@number` tokens in the body tap-able inside a group. The call returns as
        soon as the message is on the channel send queue, which spaces and jitters sends to look human;
        delivery receipts arrive later as `messages.status` events.
      x-wapito:
        feature: send-message-without-template
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendTextRequest"
            example:
              to: "+15551234567"
              body: "Your order #4182 has shipped. Track it here: https://acme.example/t/4182"
              typing_time: 3
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: text
                timestamp: 1789459200000
                source: api
                text:
                  body: "Your order #4182 has shipped. Track it here: https://acme.example/t/4182"
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/image:
    post:
      operationId: sendImage
      tags:
        - messages
      summary: Send an image
      description: >-
        Sends a JPEG, PNG or WebP image with an optional caption. Pass a public HTTPS URL, a base64 payload or
        a media id returned by an earlier upload; Wapito fetches, validates and forwards the bytes so your
        file never has to be reachable from WhatsApp itself. Images over the plan cap are rejected with
        `payload_too_large` before any WhatsApp traffic happens.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/labels/4182.png
              caption: "Your shipping label for order #4182"
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: image
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                image:
                  id: med_01JRQ9AAB2C3D4E5F6G7H8J9K0
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9AAB2C3D4E5F6G7H8J9K0?exp=1790064200&sig=3a6f0e12c8b45d97
                  mime_type: image/png
                  file_size: 90211
                  file_name: 4182.png
                  caption: "Your shipping label for order #4182"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/video:
    post:
      operationId: sendVideo
      tags:
        - messages
      summary: Send a video
      description: >-
        Sends an MP4 video with an optional caption. WhatsApp re-encodes on the recipient device, so keep
        clips short and use H.264 with AAC audio for the widest compatibility. Very large files are the most
        common cause of slow sends: upload once with the media endpoint and reuse the returned media id across
        recipients rather than re-uploading the same bytes.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/clips/unboxing.mp4
              caption: How to set up your vice in 40 seconds
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: video
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                video:
                  id: med_01JRQ9BBC3D4E5F6G7H8J9K0L1
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9BBC3D4E5F6G7H8J9K0L1?exp=1790064260&sig=71c0a9e4b2d63f85
                  mime_type: video/mp4
                  file_size: 3184922
                  file_name: unboxing.mp4
                  caption: How to set up your vice in 40 seconds
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/document:
    post:
      operationId: sendDocument
      tags:
        - messages
      summary: Send a document
      description: >-
        Sends any file as a document bubble: PDFs, spreadsheets, archives and anything else WhatsApp does not
        render inline. The file name the recipient sees comes from `filename`, falling back to the name in the
        URL, so set it explicitly for invoices and statements where the name carries meaning. Documents keep
        their bytes intact and are never re-encoded.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/invoices/4182.pdf
              filename: Invoice-4182.pdf
              caption: "Invoice for order #4182"
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: document
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                document:
                  id: med_01JRQ9CCD4E5F6G7H8J9K0L1M2
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9CCD4E5F6G7H8J9K0L1M2?exp=1790064320&sig=b48d2ca07f931e56
                  mime_type: application/pdf
                  file_size: 48210
                  file_name: Invoice-4182.pdf
                  caption: "Invoice for order #4182"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/sticker:
    post:
      operationId: sendSticker
      tags:
        - messages
      summary: Send a sticker
      description: >-
        Sends a sticker. WhatsApp expects a 512 by 512 WebP file, static or animated, with a transparent
        background; Wapito converts PNG and JPEG input for you but cannot rescale an animated file, so prepare
        animated stickers at the right size. Captions are ignored on stickers because WhatsApp has nowhere to
        render them.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/stickers/thumbs-up.webp
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: sticker
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                sticker:
                  id: med_01JRQ9DDE5F6G7H8J9K0L1M2N3
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9DDE5F6G7H8J9K0L1M2N3?exp=1790064380&sig=2e7b95c1a03d648f
                  mime_type: image/webp
                  file_size: 40118
                  file_name: thumbs-up.webp
                  caption: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/audio:
    post:
      operationId: sendAudio
      tags:
        - messages
      summary: Send an audio file
      description: >-
        Sends an audio file as a normal attachment with a play button and a visible file name, which is what
        you want for podcasts, call recordings and music. It is not a voice note: the recipient sees a file
        bubble rather than a waveform. Use the voice endpoint when you want the message to look like it was
        recorded in the app.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/audio/briefing.mp3
              filename: Morning briefing.mp3
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: audio
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                audio:
                  id: med_01JRQ9EEF6G7H8J9K0L1M2N3P4
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9EEF6G7H8J9K0L1M2N3P4?exp=1790064440&sig=9d31f76e04ba28c5
                  mime_type: audio/mpeg
                  file_size: 1920411
                  file_name: Morning briefing.mp3
                  caption: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/voice:
    post:
      operationId: sendVoice
      tags:
        - messages
      summary: Send a voice note
      description: >-
        Sends a voice note: the waveform bubble WhatsApp shows for messages recorded in the app, complete with
        a played receipt once the recipient listens. Wapito converts your input to the Opus in OGG format
        WhatsApp requires, so MP3, WAV and M4A all work. Voice notes feel personal and get read, which also
        makes them easy to overuse in outreach.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/audio/reply-4182.m4a
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: voice
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                voice:
                  id: med_01JRQ9FFG7H8J9K0L1M2N3P4Q5
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9FFG7H8J9K0L1M2N3P4Q5?exp=1790064500&sig=6c04e83b15af729d
                  mime_type: audio/ogg; codecs=opus
                  file_size: 84221
                  file_name: null
                  caption: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/short:
    post:
      operationId: sendShort
      tags:
        - messages
      summary: Send a short video note
      description: >-
        Sends a round short video note, the video equivalent of a voice note that WhatsApp plays in a circular
        bubble. The source must be a square MP4 of at most sixty seconds; longer or non-square input is
        rejected by WhatsApp rather than cropped. Short notes are a high-attention format best kept for
        replies inside an existing conversation.
      x-wapito:
        feature: send-media
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMediaRequest"
            example:
              to: "+15551234567"
              media: https://acme.example/clips/hello-square.mp4
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: video
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                video:
                  id: med_01JRQ9GGH8J9K0L1M2N3P4Q5R6
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ9GGH8J9K0L1M2N3P4Q5R6?exp=1790064560&sig=ad61e3902c745b18
                  mime_type: video/mp4
                  file_size: 1204881
                  file_name: hello-square.mp4
                  caption: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/link:
    post:
      operationId: sendLink
      tags:
        - messages
      summary: Send a link with a custom preview
      description: >-
        Sends a text message whose link preview you supply instead of letting WhatsApp scrape the target page.
        Set the headline, description and thumbnail yourself when the destination is behind a login, renders
        client-side, or simply produces an unflattering card. The message still counts as a normal text
        message for quota and anti-ban purposes, and the recipient sees an ordinary rich link bubble.
      x-wapito:
        feature: send-text
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendLinkRequest"
            example:
              to: "+15551234567"
              url: https://acme.example/t/4182
              title: "Track order #4182"
              description: Out for delivery, arriving before 18:00.
              image: https://acme.example/og/tracking.png
              body: "Your parcel is on the van:"
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: text
                timestamp: 1789459200000
                source: api
                text:
                  body: "Your parcel is on the van: https://acme.example/t/4182"
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/location:
    post:
      operationId: sendLocation
      tags:
        - messages
      summary: Send a location pin
      description: >-
        Sends a static location pin with an optional place name and street address, which the recipient can
        open in their maps app or use for directions. Coordinates are decimal degrees. This is a one-off pin
        rather than live location sharing, which WhatsApp only offers from the app and no unofficial engine
        can start on your behalf.
      x-wapito:
        feature: send-location-contact
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendLocationRequest"
            example:
              to: "+15551234567"
              latitude: 53.3811
              longitude: -1.4701
              name: Acme Supplies
              address: 14 Forge Lane, Sheffield S3 8GG
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: location
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                location:
                  latitude: 53.3811
                  longitude: -1.4701
                  name: Acme Supplies
                  address: 14 Forge Lane, Sheffield S3 8GG
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/contact:
    post:
      operationId: sendContact
      tags:
        - messages
      summary: Send a contact card
      description: >-
        Sends a contact card the recipient can save with one tap. Supply a ready vCard when you control the
        formatting, or a contact id and display name and let Wapito build the card for you. Sending your
        support number as a card at the end of an order confirmation is a reliable way to get saved into the
        address book, which reduces spam reports later.
      x-wapito:
        feature: send-location-contact
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendContactRequest"
            example:
              to: "+15551234567"
              contact_id: "+15557654321"
              name: Acme Support
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: contact
                timestamp: 1789459200000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
                contact:
                  vcard: |-
                    BEGIN:VCARD
                    VERSION:3.0
                    FN:Acme Support
                    TEL;type=CELL;waid=15557654321:+1 555 765 4321
                    END:VCARD
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/poll:
    post:
      operationId: sendPoll
      tags:
        - messages
      summary: Send a poll
      description: >-
        Sends a poll with between two and twelve options, optionally allowing multiple selections. Votes are
        never returned by this call: they arrive afterwards as `polls` webhook events carrying the voter and
        their complete current selection, so store results keyed by voter rather than incrementing counters.
        Polls work in direct chats and groups and are a low-friction way to collect structured answers.
      x-wapito:
        feature: send-poll
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
          - polls
        errors:
          - invalid_recipient
          - channel_not_connected
          - reachout_timelocked
          - send_rate_limited
          - cold_send_limit
          - warmup_limit
          - quota_exceeded
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendPollRequest"
            example:
              to: 120363041234567890@g.us
              title: When should we run the launch standup?
              options:
                - Monday 09:00
                - Tuesday 10:00
                - Wednesday 16:00
              multiple: false
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_120363041234567890@g.us_5C71E90AB3F4D26178BB
                chat_id: 120363041234567890@g.us
                from_me: true
                from: 120363041234567890@g.us
                from_lid: null
                from_name: Acme Support
                participant: "15557654321"
                type: poll
                timestamp: 1789459410000
                source: api
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions:
                    - "15551234567"
                status: sent
                poll:
                  title: When should we run the launch standup?
                  options:
                    - Monday 09:00
                    - Tuesday 10:00
                    - Wednesday 16:00
                  multiple: false
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/{id}/react:
    post:
      operationId: reactToMessage
      tags:
        - messages
      summary: React to a message
      description: >-
        Adds an emoji reaction to a message in any chat the channel takes part in. Sending a different emoji
        replaces the previous reaction rather than adding a second one, because WhatsApp allows one reaction
        per person per message. Reactions do not count against the send quota and carry almost no ban risk,
        which makes them a cheap way to acknowledge inbound messages instantly.
      x-wapito:
        feature: react-to-message
        ban_risk: low
        webhooks:
          - messages.reactions
        errors:
          - message_not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReactRequest"
            example:
              emoji: 👍
      responses:
        "200":
          description: The reaction was sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: removeReaction
      tags:
        - messages
      summary: Remove a reaction from a message
      description: >-
        Removes the reaction this channel previously placed on a message. It is equivalent to reacting with an
        empty emoji and is safe to call when no reaction exists, in which case WhatsApp simply does nothing.
        Other people keep their own reactions; a channel can only clear its own.
      x-wapito:
        feature: react-to-message
        ban_risk: low
        webhooks:
          - messages.reactions
        errors:
          - message_not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      responses:
        "204":
          description: The reaction was removed.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/list/{chat_id}:
    get:
      operationId: listMessages
      tags:
        - messages
      summary: List messages in a chat
      description: >-
        Returns a page of messages from one chat, newest first, with filters for direction and time window.
        History comes from the engine store, so a channel only sees messages received or sent since it
        connected, and only when engine storage is enabled. Treat this as a convenience for recent context,
        not as an archive: keep your own copy of anything you need permanently.
      x-wapito:
        feature: read-messages
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: chat_id
          in: path
          required: true
          description: Chat to read, in any accepted recipient form.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
        - name: from_me
          in: query
          required: false
          description: Return only outbound (`true`) or only inbound (`false`) messages.
          schema:
            type: boolean
            examples:
              - true
        - name: time_from
          in: query
          required: false
          description: Only messages at or after this Unix epoch millisecond timestamp.
          schema:
            type: integer
            examples:
              - 1789372800000
        - name: time_to
          in: query
          required: false
          description: Only messages at or before this Unix epoch millisecond timestamp.
          schema:
            type: integer
            examples:
              - 1789459200000
      responses:
        "200":
          description: A page of messages.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageList"
              example:
                messages:
                  - id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                    chat_id: 15551234567@s.whatsapp.net
                    from_me: false
                    from: "15551234567"
                    from_lid: 187264518273645@lid
                    from_name: Dana Whitfield
                    participant: null
                    type: image
                    timestamp: 1789459321000
                    source: app
                    image:
                      id: med_01JRQ8F4X9N2K7YB3C5V6W8H0T
                      link: >-
                        https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35
                      mime_type: image/jpeg
                      file_size: 184213
                      file_name: receipt.jpg
                      caption: Here is the receipt you asked for
                    context:
                      quoted_id: null
                      quoted_author: null
                      forwarded: false
                      mentions: []
                    status: delivered
                  - id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                    chat_id: 15551234567@s.whatsapp.net
                    from_me: true
                    from: "15557654321"
                    from_lid: null
                    from_name: Acme Support
                    participant: null
                    type: text
                    timestamp: 1789459200000
                    source: api
                    text:
                      body: "Your order #4182 has shipped. Track it here: https://acme.example/t/4182"
                    context:
                      quoted_id: null
                      quoted_author: null
                      forwarded: false
                      mentions: []
                    status: sent
                count: 2
                total: 184
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/{id}:
    get:
      operationId: getMessage
      tags:
        - messages
      summary: Get a single message
      description: >-
        Returns one message by id in the same shape webhook events use, which makes it the easiest way to
        re-read something you failed to process the first time. The message must still be in the engine store,
        so a lookup can fail with `message_not_found` for anything sent before this channel connected or
        beyond the retention window of the engine.
      x-wapito:
        feature: read-messages
        ban_risk: low
        webhooks: []
        errors:
          - message_not_found
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      responses:
        "200":
          description: The message.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                chat_id: 15551234567@s.whatsapp.net
                from_me: false
                from: "15551234567"
                from_lid: 187264518273645@lid
                from_name: Dana Whitfield
                participant: null
                type: image
                timestamp: 1789459321000
                source: app
                image:
                  id: med_01JRQ8F4X9N2K7YB3C5V6W8H0T
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35
                  mime_type: image/jpeg
                  file_size: 184213
                  file_name: receipt.jpg
                  caption: Here is the receipt you asked for
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: delivered
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteMessage
      tags:
        - messages
      summary: Delete a message for everyone
      description: >-
        Revokes a message the channel sent so it disappears for every participant and leaves the familiar
        deleted placeholder. WhatsApp only allows this for your own messages and only for a limited window
        after sending, so late calls fail with `message_not_found`. The revocation is broadcast to your
        webhooks as a `messages.deleted` event as well.
      x-wapito:
        feature: edit-delete-message
        ban_risk: low
        webhooks:
          - messages.deleted
        errors:
          - message_not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      responses:
        "204":
          description: The message was revoked for everyone.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: editMessage
      tags:
        - messages
      summary: Edit a message you sent
      description: >-
        Replaces the text of a message the channel already sent; recipients see the new body with an edited
        marker. Only text messages are editable, only your own, and only inside the window WhatsApp allows
        after sending. Editing is a much better fix for a typo in a broadcast than deleting and resending,
        which doubles the notifications recipients receive.
      x-wapito:
        feature: edit-delete-message
        ban_risk: low
        webhooks:
          - messages.edited
        errors:
          - invalid_request
          - message_not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EditMessageRequest"
            example:
              body: "Your order #4182 has shipped. Tracking: https://acme.example/t/4182"
      responses:
        "200":
          description: The message after the edit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: text
                timestamp: 1789459200000
                source: api
                text:
                  body: "Your order #4182 has shipped. Track it here: https://acme.example/t/4182"
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/{id}/read:
    put:
      operationId: markMessageRead
      tags:
        - messages
      summary: Mark a message as read
      description: >-
        Sends a read receipt for a message, turning the blue ticks on for the sender and clearing the unread
        badge for that chat on the linked phone. Marking inbound messages read before replying is what a human
        does, so the send queue uses the same order; enable `auto_read` in the channel settings if you want
        every inbound message acknowledged without an extra call.
      x-wapito:
        feature: read-messages
        ban_risk: low
        webhooks:
          - messages.status
        errors:
          - message_not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      responses:
        "204":
          description: The read receipt was sent.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/{id}/forward:
    post:
      operationId: forwardMessage
      tags:
        - messages
      summary: Forward a message to another chat
      description: >-
        Forwards an existing message to another chat, keeping the original media without re-uploading it and
        marking the result as forwarded for the recipient. The default GOWS engine cannot forward, so a
        channel on GOWS gets `engine_unsupported_feature` with a hint to switch; NOWEB channels forward
        normally. A forward counts against the send quota exactly like a new message.
      x-wapito:
        feature: forward-message
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_recipient
          - message_not_found
          - engine_unsupported_feature
          - channel_not_connected
          - quota_exceeded
        former_ids: []
        quota: sent
        engines:
          gows: "no"
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ForwardRequest"
            example:
              to: 120363041234567890@g.us
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_120363041234567890@g.us_5C71E90AB3F4D26178BB
                chat_id: 120363041234567890@g.us
                from_me: true
                from: 120363041234567890@g.us
                from_lid: null
                from_name: Acme Support
                participant: "15557654321"
                type: text
                timestamp: 1789459410000
                source: api
                text:
                  body: Standup starts in 10 minutes @15551234567
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: true
                  mentions: []
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "423":
          $ref: "#/components/responses/Locked"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/{id}/star:
    put:
      operationId: starMessage
      tags:
        - messages
      summary: Star or unstar a message
      description: >-
        Stars a message so it appears in the starred list on the linked phone, or removes the star again. It
        is a purely local bookmark: nobody else can see it and no notification is produced. Only the NOWEB
        engine implements starring, so GOWS channels receive `engine_unsupported_feature` with a hint to
        switch engines.
      x-wapito:
        feature: read-messages
        ban_risk: low
        webhooks: []
        errors:
          - message_not_found
          - engine_unsupported_feature
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StarRequest"
            example:
              star: true
      responses:
        "200":
          description: The star was applied or removed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/interactive:
    post:
      operationId: sendInteractive
      tags:
        - messages
      summary: Send an interactive button message
      description: >-
        Sends a message with up to three tappable buttons, so the recipient answers without typing. This
        endpoint is reserved and returns `501 feature_not_available` today: neither the GOWS nor the NOWEB
        engine can produce interactive messages that render reliably on current WhatsApp clients. The route
        exists so the contract, SDKs and your integration code are ready when engine support lands.
      x-phase: 2
      x-wapito:
        feature: send-message-without-template
        ban_risk: medium
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InteractiveMessageRequest"
            example:
              to: "+15551234567"
              body: Did the courier reach you today?
              buttons:
                - id: "yes"
                  title: Yes, thanks
                  type: reply
                - id: "no"
                  title: Not yet
                  type: reply
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: text
                timestamp: 1789459200000
                source: api
                text:
                  body: "Your order #4182 has shipped. Track it here: https://acme.example/t/4182"
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/carousel:
    post:
      operationId: sendCarousel
      tags:
        - messages
      summary: Send a carousel message
      description: >-
        Sends a horizontally scrollable set of media cards, the format catalogues and multi-product promotions
        use. This endpoint is reserved and returns `501 feature_not_available` today because no supported
        engine can build a carousel payload WhatsApp accepts. Send several media messages or a catalog link
        instead until engine support exists.
      x-phase: 2
      x-wapito:
        feature: send-message-without-template
        ban_risk: medium
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CarouselMessageRequest"
            example:
              to: "+15551234567"
              body: This week in the workshop
              cards:
                - media: https://acme.example/p/vice.jpg
                  title: Bench vice 100 mm
                  description: GBP 49.00
                - media: https://acme.example/p/clamp.jpg
                  title: F-clamp 300 mm
                  description: GBP 12.50
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from_me: true
                from: "15557654321"
                from_lid: null
                from_name: Acme Support
                participant: null
                type: text
                timestamp: 1789459200000
                source: api
                text:
                  body: "Your order #4182 has shipped. Track it here: https://acme.example/t/4182"
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/quiz:
    post:
      operationId: sendQuiz
      tags:
        - messages
      summary: Send a quiz message
      description: >-
        Sends a poll with one correct answer and an explanation revealed after voting, the format WhatsApp
        uses for quizzes. This endpoint is reserved and returns `501 feature_not_available` today because the
        quiz payload is not implemented by either engine. A normal poll sent through the poll endpoint is the
        supported alternative, with correctness scored in your own code.
      x-phase: 2
      x-wapito:
        feature: send-poll
        ban_risk: medium
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuizMessageRequest"
            example:
              to: 120363041234567890@g.us
              title: Which thread does a bench vice use?
              options:
                - Acme
                - Metric coarse
                - BSP
              correct_option: 0
              explanation: Bench vices use an Acme thread because it handles axial load well.
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_120363041234567890@g.us_5C71E90AB3F4D26178BB
                chat_id: 120363041234567890@g.us
                from_me: true
                from: 120363041234567890@g.us
                from_lid: null
                from_name: Acme Support
                participant: "15557654321"
                type: text
                timestamp: 1789459410000
                source: api
                text:
                  body: Standup starts in 10 minutes @15551234567
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions:
                    - "15551234567"
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/event:
    post:
      operationId: sendEvent
      tags:
        - messages
      summary: Send a group event invitation
      description: >-
        Creates a WhatsApp event in a group with a name, a start time and an optional call link, so
        participants can respond that they are going. This endpoint is reserved and returns `501
        feature_not_available` today because neither engine implements the event payload. Post a poll or a
        plain message with the details until support arrives.
      x-phase: 2
      x-wapito:
        feature: group-events
        ban_risk: medium
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EventMessageRequest"
            example:
              to: 120363041234567890@g.us
              name: Launch retro
              description: What went well, what did not.
              start_at: "2026-09-22T15:00:00.000Z"
              location: Sheffield office, room 2
      responses:
        "201":
          description: >-
            The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later
            as `messages.status` events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
              example:
                id: true_120363041234567890@g.us_5C71E90AB3F4D26178BB
                chat_id: 120363041234567890@g.us
                from_me: true
                from: 120363041234567890@g.us
                from_lid: null
                from_name: Acme Support
                participant: "15557654321"
                type: text
                timestamp: 1789459410000
                source: api
                text:
                  body: Standup starts in 10 minutes @15551234567
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions:
                    - "15551234567"
                status: sent
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /messages/{id}/pin:
    post:
      operationId: pinMessage
      tags:
        - messages
      summary: Pin a message in a chat
      description: >-
        Pins a message to the top of a chat for a chosen duration, which groups use to keep rules or a
        schedule visible. This endpoint is reserved and returns `501 feature_not_available` today because
        pinning is not exposed by either engine. Editing the group description is the supported way to keep
        something permanently visible to every participant.
      x-phase: 2
      x-wapito:
        feature: edit-delete-message
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      responses:
        "201":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: unpinMessage
      tags:
        - messages
      summary: Unpin a message in a chat
      description: >-
        Removes a pin from a message so the chat stops showing it at the top. This endpoint is reserved and
        returns `501 feature_not_available` today, for the same reason as pinning: neither the GOWS nor the
        NOWEB engine exposes the pin protocol message. It is documented so the contract does not change shape
        when support lands.
      x-phase: 2
      x-wapito:
        feature: edit-delete-message
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Message id as returned by a send call or delivered in a `messages` webhook event.
          schema:
            type: string
            examples:
              - true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /chats:
    get:
      operationId: listChats
      tags:
        - chats
      summary: List chats
      description: >-
        Returns a page of the conversations the channel takes part in, most recently active first, covering
        direct chats, groups and WhatsApp Channels. The list is built from the engine store, so it only
        reflects chats the engine has observed and requires engine storage to be enabled. Use it to populate
        an inbox view, and keep your own copy if you need history older than the engine keeps.
      x-wapito:
        feature: chats-list
        ban_risk: low
        webhooks: []
        errors:
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: A page of chats.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatList"
              example:
                chats:
                  - id: 15551234567@s.whatsapp.net
                    name: Dana Whitfield
                    type: individual
                    unread_count: 2
                    archived: false
                    pinned: false
                    muted_until: null
                    timestamp: 1789459321000
                    picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
                    last_message: null
                count: 1
                total: 96
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /chats/{id}:
    get:
      operationId: getChat
      tags:
        - chats
      summary: Get a chat
      description: >-
        Returns one conversation with its display name, unread count, archive and pin state and the timestamp
        of the latest message. Pass a phone number, a group id or a channel id; Wapito normalises whichever
        form you use. A chat that the engine has never observed is reported as `not_found` rather than
        invented as an empty shell.
      x-wapito:
        feature: chats-list
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Chat id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      responses:
        "200":
          description: The chat.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chat"
              example:
                id: 15551234567@s.whatsapp.net
                name: Dana Whitfield
                type: individual
                unread_count: 2
                archived: false
                pinned: false
                muted_until: null
                timestamp: 1789459321000
                picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
                last_message: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteChat
      tags:
        - chats
      summary: Delete a chat
      description: >-
        Removes the conversation and its local history from the linked phone. It deletes nothing on the other
        side and it does not leave a group, block a contact or unfollow a channel; use the dedicated endpoints
        for those. The deletion is local and irreversible, so archive instead when you only want the chat out
        of the way.
      x-wapito:
        feature: chats-list
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Chat id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      responses:
        "204":
          description: The chat was deleted from the linked phone.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: updateChat
      tags:
        - chats
      summary: Mark a chat read or unread
      description: >-
        Sets or clears the unread badge on a chat. Marking a chat unread is a purely local flag that does not
        notify the other side, which makes it a convenient way to hand a conversation back to a human operator
        from an automated workflow. Clearing it does not send read receipts; use the message read endpoint for
        that.
      x-wapito:
        feature: chats-list
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - not_found
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Chat id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateChatRequest"
            example:
              unread: true
      responses:
        "200":
          description: The chat after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chat"
              example:
                id: 15551234567@s.whatsapp.net
                name: Dana Whitfield
                type: individual
                unread_count: 1
                archived: false
                pinned: false
                muted_until: null
                timestamp: 1789459321000
                picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
                last_message: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /chats/{id}/picture:
    get:
      operationId: getChatPicture
      tags:
        - chats
      summary: Get the picture of a chat
      description: >-
        Returns the profile picture of the chat: the contact photo for a direct chat, the icon for a group or
        the cover image for a WhatsApp Channel. WhatsApp only serves pictures the account is allowed to see,
        so a contact with restrictive privacy settings resolves to `null` rather than an error. The URL points
        at the WhatsApp CDN and expires.
      x-wapito:
        feature: profile-picture
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: partial
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Chat id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      responses:
        "200":
          description: The picture URL, or `null` when none is visible to this account.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PictureResult"
              example:
                url: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /chats/{id}/archive:
    patch:
      operationId: archiveChat
      tags:
        - chats
      summary: Archive or unarchive a chat
      description: >-
        Moves a conversation into the archive on the linked phone, or brings it back to the main list.
        Archiving is local and silent: the other side sees nothing and messages still arrive normally. Only
        the NOWEB engine implements it, so a channel on the default GOWS engine receives
        `engine_unsupported_feature` together with a hint to switch engines.
      x-wapito:
        feature: chats-list
        ban_risk: low
        webhooks:
          - chats
        errors:
          - invalid_request
          - not_found
          - engine_unsupported_feature
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Chat id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArchiveChatRequest"
            example:
              archive: true
      responses:
        "200":
          description: The chat after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chat"
              example:
                id: 15551234567@s.whatsapp.net
                name: Dana Whitfield
                type: individual
                unread_count: 2
                archived: true
                pinned: false
                muted_until: null
                timestamp: 1789459321000
                picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
                last_message: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts:
    get:
      operationId: listContacts
      tags:
        - contacts
      summary: List contacts
      description: >-
        Returns a page of the contacts known to the linked account, combining the phone address book with
        everyone who has ever written to the channel. Entries carry both the phone number and, where WhatsApp
        used one, the LID. This is the bulk export endpoint; use the single-contact endpoints when you already
        know who you are looking for.
      x-wapito:
        feature: contacts-list
        ban_risk: low
        webhooks: []
        errors:
          - channel_not_connected
          - engine_unsupported_feature
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: partial
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: A page of contacts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactList"
              example:
                contacts:
                  - id: 15551234567@s.whatsapp.net
                    phone: "15551234567"
                    lid: 187264518273645@lid
                    name: Dana Whitfield
                    push_name: Dana
                    short_name: Dana
                    is_business: false
                    is_me: false
                    is_blocked: false
                    picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
                count: 1
                total: 1204
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/{id}:
    get:
      operationId: getContact
      tags:
        - contacts
      summary: Get a contact
      description: >-
        Returns one contact by phone number, WhatsApp id or LID, with the name from the address book, the push
        name the contact publishes, whether the account is a Business account and whether this channel has
        blocked it. Use it to enrich an inbound message with a display name before showing the conversation to
        an operator.
      x-wapito:
        feature: contacts-list
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "200":
          description: The contact.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
              example:
                id: 15551234567@s.whatsapp.net
                phone: "15551234567"
                lid: 187264518273645@lid
                name: Dana Whitfield
                push_name: Dana
                short_name: Dana
                is_business: false
                is_me: false
                is_blocked: false
                picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: updateContact
      tags:
        - contacts
      summary: Rename a contact
      description: >-
        Writes a name into the address book of the linked phone so the contact shows up with a useful label in
        WhatsApp itself and in every chat list this channel exposes. Only the NOWEB engine can write contacts,
        so GOWS channels receive `engine_unsupported_feature` with a hint to switch. It changes nothing on the
        contact side.
      x-wapito:
        feature: contacts-list
        ban_risk: low
        webhooks:
          - contacts
        errors:
          - invalid_request
          - not_found
          - engine_unsupported_feature
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateContactRequest"
            example:
              first_name: Dana
              last_name: Whitfield
      responses:
        "200":
          description: The contact after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
              example:
                id: 15551234567@s.whatsapp.net
                phone: "15551234567"
                lid: 187264518273645@lid
                name: Dana Whitfield
                push_name: Dana
                short_name: Dana
                is_business: false
                is_me: false
                is_blocked: false
                picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/{id}/profile:
    get:
      operationId: getContactProfile
      tags:
        - contacts
      summary: Get a contact profile picture
      description: >-
        Returns the profile picture of a contact as a WhatsApp CDN URL. Privacy settings decide what you get:
        a contact who restricts their photo to their own contacts resolves to `null` for an unknown business
        number, which is normal and not an error. Download and store the image yourself if you need it beyond
        the short CDN lifetime.
      x-wapito:
        feature: profile-picture
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "200":
          description: The picture URL, or `null` when the contact hides it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PictureResult"
              example:
                url: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/check:
    post:
      operationId: checkContacts
      tags:
        - contacts
      summary: Check numbers for WhatsApp registration
      description: >-
        Checks up to fifty phone numbers in one request and reports, for each, whether it is registered on
        WhatsApp and what its canonical id is. Wapito paces the underlying lookups at a low concurrency
        because bulk checking is one of the fastest ways to get a number flagged. Every submitted number
        consumes one check quota unit whether or not it exists.
      x-wapito:
        feature: check-number
        ban_risk: high
        webhooks: []
        errors:
          - invalid_request
          - channel_not_connected
          - quota_exceeded
          - rate_limited
        former_ids: []
        quota: chk
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckContactsRequest"
            example:
              phones:
                - "+15551234567"
                - "+15559876543"
                - "+15550000000"
      responses:
        "200":
          description: One result per submitted number, in submission order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactCheckBatch"
              example:
                checked: 3
                found: 2
                results:
                  - phone: "+15551234567"
                    exists: true
                    jid: 15551234567@s.whatsapp.net
                    lid: 187264518273645@lid
                  - phone: "+15559876543"
                    exists: true
                    jid: 15559876543@s.whatsapp.net
                    lid: null
                  - phone: "+15550000000"
                    exists: false
                    jid: null
                    lid: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/{id}/exists:
    get:
      operationId: checkContactExists
      tags:
        - contacts
      summary: Check one number for WhatsApp
      description: >-
        Checks a single phone number and reports whether it is registered on WhatsApp, with its canonical id
        when it is. This is the endpoint behind a signup form that needs to validate a number before promising
        to message it. It consumes one check quota unit per call; use the batch endpoint when you have a list
        rather than looping over this one.
      x-wapito:
        feature: check-number
        ban_risk: high
        webhooks: []
        errors:
          - invalid_recipient
          - channel_not_connected
          - quota_exceeded
          - rate_limited
        former_ids: []
        quota: chk
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "200":
          description: Whether the number is on WhatsApp.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactCheckResult"
              example:
                phone: "+15551234567"
                exists: true
                jid: 15551234567@s.whatsapp.net
                lid: 187264518273645@lid
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/{id}/lid:
    get:
      operationId: getContactLid
      tags:
        - contacts
      summary: Resolve a phone number to a LID
      description: >-
        Returns the linked device identifier WhatsApp uses for a phone number. LIDs replace phone numbers in
        more and more protocol traffic, so you need this mapping to join a LID-addressed event to the customer
        record you already hold. Wapito caches the result, which makes repeated lookups cheap during a
        migration.
      x-wapito:
        feature: lid-to-phone
        ban_risk: low
        webhooks: []
        errors:
          - invalid_recipient
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "200":
          description: The LID for the number.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LidResult"
              example:
                lid: 187264518273645@lid
                phone: "15551234567"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/lid/{lid}:
    get:
      operationId: getContactByLid
      tags:
        - contacts
      summary: Resolve a LID to a phone number
      description: >-
        Returns the contact behind a linked device identifier, which is the lookup you need when an event
        arrives carrying only a LID. Wapito tries the cached mapping first and asks the engine when it misses,
        then remembers the answer. Some LIDs never resolve, because the account hides its number, in which
        case the response is `not_found`.
      x-wapito:
        feature: lid-to-phone
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: partial
      parameters:
        - name: lid
          in: path
          required: true
          description: Linked device identifier, with or without the `@lid` suffix.
          schema:
            type: string
            examples:
              - 187264518273645@lid
      responses:
        "200":
          description: The contact behind the LID.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
              example:
                id: 15551234567@s.whatsapp.net
                phone: "15551234567"
                lid: 187264518273645@lid
                name: Dana Whitfield
                push_name: Dana
                short_name: Dana
                is_business: false
                is_me: false
                is_blocked: false
                picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /contacts/{id}/about:
    get:
      operationId: getContactAbout
      tags:
        - contacts
      summary: Get the about text of a contact
      description: >-
        Returns the about line a contact publishes on their profile. This endpoint is reserved and returns
        `501 feature_not_available` today because neither engine exposes the about query. The rest of the
        profile is available through the contact and profile picture endpoints, which cover the fields most
        integrations actually display.
      x-phase: 2
      x-wapito:
        feature: contacts-list
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AboutResult"
              example:
                about: Replies Mon-Fri, 9 to 6 UK time.
                set_at: "2026-04-02T08:00:00.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /blacklist/{id}:
    put:
      operationId: blockContact
      tags:
        - contacts
      summary: Block a contact
      description: >-
        Adds a contact to the block list of the linked account so their messages and calls stop arriving. This
        endpoint is reserved and returns `501 feature_not_available` today because blocking is not exposed by
        either engine. Until it lands, drop unwanted senders in your own code by filtering the webhook stream
        before it reaches your workflow.
      x-phase: 2
      x-wapito:
        feature: contacts-list
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: 15551234567@s.whatsapp.net
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: unblockContact
      tags:
        - contacts
      summary: Unblock a contact
      description: >-
        Removes a contact from the block list of the linked account so their messages arrive again. This
        endpoint is reserved and returns `501 feature_not_available` today, for the same reason as blocking:
        no supported engine implements the block list protocol. It is documented now so the contract does not
        change when support arrives.
      x-phase: 2
      x-wapito:
        feature: contacts-list
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Contact id, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /presence/me:
    post:
      operationId: setMyPresence
      tags:
        - presence
      summary: Set the channel online or offline
      description: >-
        Publishes the global presence of the linked account. Going online makes the account visible as
        available to everyone who can see your presence and lets WhatsApp deliver presence updates for chats
        you subscribe to; going offline hides it. Automations that only send messages should stay offline,
        because an account that is permanently online but never types looks synthetic.
      x-wapito:
        feature: presence-typing
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MyPresenceRequest"
            example:
              presence: online
      responses:
        "204":
          description: Presence published.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /presence/{chat_id}:
    post:
      operationId: setChatPresence
      tags:
        - presence
      summary: Show typing or recording in a chat
      description: >-
        Publishes a typing, recording or paused indicator in one chat. The indicator is held until your
        next presence write for that chat, so send `{presence: "paused"}` when you are done or the chat
        stays on typing. Most integrations should not call this directly: setting `typing_time` on a send,
        or leaving typing simulation on `auto`, produces the same human rhythm (start, wait, stop, send)
        without extra round trips.
      x-wapito:
        feature: presence-typing
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - invalid_recipient
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: chat_id
          in: path
          required: true
          description: Chat to publish the indicator in.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatPresenceRequest"
            example:
              presence: typing
      responses:
        "204":
          description: Indicator published.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    get:
      operationId: getChatPresence
      tags:
        - presence
      summary: Get presence for a chat
      description: >-
        Returns the last presence Wapito observed for a chat, including per-participant state in groups and
        the last-seen timestamp when the contact publishes it. You only get data for chats you subscribed to,
        and contacts who hide last seen never expose a timestamp. Treat the value as a hint that may be
        seconds or minutes stale.
      x-wapito:
        feature: presence-typing
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: chat_id
          in: path
          required: true
          description: Chat to read presence for.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      responses:
        "200":
          description: Last known presence for the chat.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceInfo"
              example:
                chat_id: 15551234567@s.whatsapp.net
                presence: typing
                last_seen: 1789459280000
                participants:
                  - id: 15551234567@s.whatsapp.net
                    presence: typing
                    last_seen: 1789459280000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /presence/{chat_id}/subscribe:
    post:
      operationId: subscribeToPresence
      tags:
        - presence
      summary: Subscribe to presence for a chat
      description: >-
        Asks WhatsApp to stream presence updates for one chat. Without a subscription no `presences` webhook
        events arrive for that chat at all, which is the single most common reason people think presence is
        broken. Subscriptions live in the engine session, so re-subscribe after a reconnect if presence
        matters to your product.
      x-wapito:
        feature: presence-typing
        ban_risk: low
        webhooks:
          - presences
        errors:
          - invalid_recipient
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: chat_id
          in: path
          required: true
          description: Chat to subscribe to.
          schema:
            type: string
            examples:
              - 15551234567@s.whatsapp.net
      responses:
        "204":
          description: Subscribed; updates now arrive as `presences` events.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /presence:
    get:
      operationId: listPresences
      tags:
        - presence
      summary: List presence for subscribed chats
      description: >-
        Returns the last known presence for every chat the channel is currently subscribed to, which is the
        cheapest way to render an online indicator across a whole inbox. Only the NOWEB engine keeps this
        list, so GOWS channels receive `engine_unsupported_feature` with a hint to switch; read presence chat
        by chat instead.
      x-wapito:
        feature: presence-typing
        ban_risk: low
        webhooks: []
        errors:
          - engine_unsupported_feature
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: supported
      responses:
        "200":
          description: Presence for every subscribed chat.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceList"
              example:
                presences:
                  - chat_id: 15551234567@s.whatsapp.net
                    presence: typing
                    last_seen: 1789459280000
                    participants:
                      - id: 15551234567@s.whatsapp.net
                        presence: typing
                        last_seen: 1789459280000
                count: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups:
    post:
      operationId: createGroup
      tags:
        - groups
      summary: Create a group
      description: >-
        Creates a WhatsApp group with a subject and an initial participant list, and optionally sets the
        description in the same call. This is the flagship capability the official Cloud API does not offer at
        all. It is also the highest ban-risk operation on this API: WhatsApp watches group creation closely,
        so create groups from a warmed-up number, keep batches small and only add people who expect it.
      x-wapito:
        feature: create-group
        ban_risk: high
        webhooks:
          - groups
          - groups.participants
        errors:
          - invalid_request
          - invalid_recipient
          - channel_not_connected
          - rate_limited
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateGroupRequest"
            example:
              subject: Acme Launch Team
              participants:
                - "+15551234567"
                - "+15559876543"
              description: Coordination for the Q3 launch. Keep it on topic.
      responses:
        "201":
          description: The group as WhatsApp created it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
              example:
                id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                owner: 15557654321@s.whatsapp.net
                created_at: "2026-08-02T09:14:00.000Z"
                participants_count: 24
                is_announce: false
                is_locked: false
                is_community_announce: false
                invite_code: HkQ2ZpL9vRtAeYm1
                picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                settings:
                  info_admin_only: true
                  messages_admin_only: false
                  member_add_mode: admin_add
                  membership_approval: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    get:
      operationId: listGroups
      tags:
        - groups
      summary: List groups
      description: >-
        Returns a page of the groups the linked account belongs to, with subject, participant count and the
        settings currently in force. Use it to reconcile your own records after an operator has created or
        left groups from the phone directly. Participant lists are not included; fetch them per group when you
        need them.
      x-wapito:
        feature: group-info
        ban_risk: low
        webhooks: []
        errors:
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: A page of groups.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupList"
              example:
                groups:
                  - id: 120363041234567890@g.us
                    subject: Acme Launch Team
                    description: Coordination for the Q3 launch. Keep it on topic.
                    owner: 15557654321@s.whatsapp.net
                    created_at: "2026-08-02T09:14:00.000Z"
                    participants_count: 24
                    is_announce: false
                    is_locked: false
                    is_community_announce: false
                    invite_code: HkQ2ZpL9vRtAeYm1
                    picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                    settings:
                      info_admin_only: true
                      messages_admin_only: false
                      member_add_mode: admin_add
                      membership_approval: true
                count: 1
                total: 12
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}:
    get:
      operationId: getGroup
      tags:
        - groups
      summary: Get a group
      description: >-
        Returns one group with its subject, description, owner, creation time, participant count and current
        security settings. Call it after any change to confirm what WhatsApp actually applied, since some
        settings silently require the channel to be an admin. The invite code is only present when the channel
        has the rights to see it.
      x-wapito:
        feature: group-info
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "200":
          description: The group.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
              example:
                id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                owner: 15557654321@s.whatsapp.net
                created_at: "2026-08-02T09:14:00.000Z"
                participants_count: 24
                is_announce: false
                is_locked: false
                is_community_announce: false
                invite_code: HkQ2ZpL9vRtAeYm1
                picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                settings:
                  info_admin_only: true
                  messages_admin_only: false
                  member_add_mode: admin_add
                  membership_approval: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: updateGroup
      tags:
        - groups
      summary: Change group subject or description
      description: >-
        Renames a group or rewrites its description. Both changes are visible to every participant as a system
        message in the chat, so avoid using the description as a scratchpad for automated state. The channel
        must be an admin, or an admin-only info setting will cause WhatsApp to reject the change with
        `forbidden`.
      x-wapito:
        feature: group-info
        ban_risk: low
        webhooks:
          - groups
        errors:
          - invalid_request
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGroupRequest"
            example:
              subject: Acme Launch Team
              description: "Launch week: daily standup at 09:30."
      responses:
        "200":
          description: The group after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
              example:
                id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                owner: 15557654321@s.whatsapp.net
                created_at: "2026-08-02T09:14:00.000Z"
                participants_count: 24
                is_announce: false
                is_locked: false
                is_community_announce: false
                invite_code: HkQ2ZpL9vRtAeYm1
                picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                settings:
                  info_admin_only: true
                  messages_admin_only: false
                  member_add_mode: admin_add
                  membership_approval: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteGroup
      tags:
        - groups
      summary: Delete a group
      description: >-
        Deletes a group outright rather than merely leaving it. This endpoint is reserved and returns `501
        feature_not_available` today, because WhatsApp itself offers no delete: a group ends when every
        participant leaves. Remove the participants and leave last if you need the equivalent effect.
      x-phase: 2
      x-wapito:
        feature: leave-group
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available`; WhatsApp has no group delete.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/settings:
    patch:
      operationId: updateGroupSettings
      tags:
        - groups
      summary: Change group security settings
      description: >-
        Sets the four WhatsApp group toggles: whether only admins may edit group info, whether only admins may
        post, whether ordinary members may add participants, and whether new joiners need admin approval.
        Turning on membership approval is the single most effective defence for a public invite link, because
        every join then lands in the applications queue instead of the group.
      x-wapito:
        feature: group-settings
        ban_risk: low
        webhooks:
          - groups
        errors:
          - invalid_request
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGroupSettingsRequest"
            example:
              messages_admin_only: false
              membership_approval: true
      responses:
        "200":
          description: The settings after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupSettings"
              example:
                info_admin_only: true
                messages_admin_only: false
                member_add_mode: admin_add
                membership_approval: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/icon:
    get:
      operationId: getGroupIcon
      tags:
        - groups
      summary: Get the group icon
      description: >-
        Returns the icon of a group as a WhatsApp CDN URL, or `null` when the group has none. The URL is
        short-lived, so download the bytes if you intend to show the icon in your own interface later. Any
        participant can read the icon; you do not need to be an admin.
      x-wapito:
        feature: group-icon
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "200":
          description: The icon URL, or `null` when the group has none.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PictureResult"
              example:
                url: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    put:
      operationId: setGroupIcon
      tags:
        - groups
      summary: Set the group icon
      description: >-
        Uploads a new icon for the group from a URL, base64 bytes or a Wapito media id. WhatsApp crops the
        image to a square and downsizes it, so supply something that still reads at 96 pixels. The channel
        must be an admin unless the group allows every member to edit its info.
      x-wapito:
        feature: group-icon
        ban_risk: low
        webhooks:
          - groups
        errors:
          - invalid_request
          - unsupported_media_type
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupIconRequest"
            example:
              media: https://acme.example/brand/launch-team.png
      responses:
        "200":
          description: The icon after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PictureResult"
              example:
                url: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteGroupIcon
      tags:
        - groups
      summary: Remove the group icon
      description: >-
        Deletes the group icon so WhatsApp falls back to the default placeholder. As with setting one, the
        channel must be an admin unless the group lets every member edit its info. There is no undo: keep your
        own copy of the image if you may want to restore it.
      x-wapito:
        feature: group-icon
        ban_risk: low
        webhooks:
          - groups
        errors:
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "204":
          description: The icon was removed.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/invite:
    get:
      operationId: getGroupInvite
      tags:
        - groups
      summary: Get the group invite link
      description: >-
        Returns the current invite code for the group together with the full `chat.whatsapp.com` link built
        from it. Only admins can read the code. Treat the link as a credential: anyone holding it can join,
        which is why pairing a public link with membership approval is the pattern that survives contact with
        the internet.
      x-wapito:
        feature: group-invite-link
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "200":
          description: The current invite code and link.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupInvite"
              example:
                code: HkQ2ZpL9vRtAeYm1
                link: https://chat.whatsapp.com/HkQ2ZpL9vRtAeYm1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: revokeGroupInvite
      tags:
        - groups
      summary: Revoke and rotate the invite link
      description: >-
        Invalidates the current invite code and generates a fresh one, which is the only way to lock out
        people who already have the old link. Everyone already in the group stays in. Rotate immediately
        whenever a link leaks, and prefer rotating over deleting and recreating a group, which loses history
        for every member.
      x-wapito:
        feature: group-invite-link
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "200":
          description: The replacement invite code and link.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupInvite"
              example:
                code: Rt9mKq2ZpLvAeYh4
                link: https://chat.whatsapp.com/Rt9mKq2ZpLvAeYh4
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/invite/accept:
    post:
      operationId: acceptGroupInvite
      tags:
        - groups
      summary: Join a group by invite link
      description: >-
        Joins the group behind an invite code. Pass the bare code or the whole `chat.whatsapp.com` link and
        Wapito extracts it. If the group has membership approval switched on, the call succeeds but the
        channel lands in the applications queue instead of the group, and you will see the join only once an
        admin approves it.
      x-wapito:
        feature: group-invite-link
        ban_risk: medium
        webhooks:
          - groups
          - groups.participants
        errors:
          - invalid_request
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AcceptInviteRequest"
            example:
              invite_code: https://chat.whatsapp.com/HkQ2ZpL9vRtAeYm1
      responses:
        "201":
          description: The group that was joined.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
              example:
                id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                owner: 15557654321@s.whatsapp.net
                created_at: "2026-08-02T09:14:00.000Z"
                participants_count: 24
                is_announce: false
                is_locked: false
                is_community_announce: false
                invite_code: HkQ2ZpL9vRtAeYm1
                picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                settings:
                  info_admin_only: true
                  messages_admin_only: false
                  member_add_mode: admin_add
                  membership_approval: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/invite/{code}:
    get:
      operationId: getGroupInviteInfo
      tags:
        - groups
      summary: Preview a group behind an invite code
      description: >-
        Looks up the subject, icon, owner and participant count of a group from its invite code without
        joining it. Use it to show the user what they are about to join, or to check that a link your workflow
        stored is still valid. A revoked or malformed code resolves to `not_found`.
      x-wapito:
        feature: group-invite-link
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: code
          in: path
          required: true
          description: Invite code, the tail of a `chat.whatsapp.com` link.
          schema:
            type: string
            examples:
              - HkQ2ZpL9vRtAeYm1
      responses:
        "200":
          description: A preview of the group.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
              example:
                id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                owner: 15557654321@s.whatsapp.net
                created_at: "2026-08-02T09:14:00.000Z"
                participants_count: 24
                is_announce: false
                is_locked: false
                is_community_announce: false
                invite_code: HkQ2ZpL9vRtAeYm1
                picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                settings:
                  info_admin_only: true
                  messages_admin_only: false
                  member_add_mode: admin_add
                  membership_approval: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/participants:
    get:
      operationId: listGroupParticipants
      tags:
        - groups
      summary: List group participants
      description: >-
        Returns every member of the group with the role WhatsApp currently assigns them, plus the phone number
        and LID where they are known. Participants who joined through a LID-only flow may have no phone
        number, which is expected on newer WhatsApp versions; resolve them through the LID endpoints when you
        need the number.
      x-wapito:
        feature: group-participants
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "200":
          description: Every participant of the group.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipantList"
              example:
                participants:
                  - id: 15551234567@s.whatsapp.net
                    phone: "15551234567"
                    lid: 187264518273645@lid
                    name: Dana Whitfield
                    role: member
                    joined_at: "2026-08-14T16:02:11.000Z"
                count: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    post:
      operationId: addGroupParticipants
      tags:
        - groups
      summary: Add participants to a group
      description: >-
        Adds up to fifty people to a group in one call. WhatsApp may silently refuse individual numbers whose
        privacy settings forbid being added by strangers; those come back in the response so you can fall back
        to sending them the invite link. Adding people who did not ask to be added is the fastest route to a
        ban, so keep batches small and get consent first.
      x-wapito:
        feature: group-participants
        ban_risk: high
        webhooks:
          - groups.participants
        errors:
          - invalid_request
          - invalid_recipient
          - not_found
          - forbidden
          - channel_not_connected
          - rate_limited
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupParticipantsRequest"
            example:
              participants:
                - "+15551234567"
                - "+15559876543"
      responses:
        "201":
          description: Per-participant outcome, including anyone WhatsApp refused to add.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipantChangeResult"
              example:
                results:
                  - id: 15551234567@s.whatsapp.net
                    status: added
                  - id: 15559876543@s.whatsapp.net
                    status: invite_required
                    reason: The contact only accepts invites from people they know.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/participants/{pid}:
    delete:
      operationId: removeGroupParticipant
      tags:
        - groups
      summary: Remove a participant from a group
      description: >-
        Removes one member from the group. The channel must be an admin, and WhatsApp does not allow removing
        another admin without demoting them first. Removal is announced in the chat as a system message, so a
        moderation bot that removes people should say why in the group or in a direct message.
      x-wapito:
        feature: group-participants
        ban_risk: medium
        webhooks:
          - groups.participants
        errors:
          - invalid_recipient
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
        - name: pid
          in: path
          required: true
          description: Participant, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "204":
          description: The participant was removed.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/admins:
    post:
      operationId: promoteGroupAdmins
      tags:
        - groups
      summary: Promote participants to admin
      description: >-
        Gives admin rights to one or more existing members so they can edit group info, manage participants
        and approve join requests. The channel must already be an admin. Promoting a second human admin in
        every group you automate is worth doing early: it keeps the group usable if the channel is ever
        disconnected or banned.
      x-wapito:
        feature: group-admins
        ban_risk: low
        webhooks:
          - groups.participants
        errors:
          - invalid_request
          - invalid_recipient
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupParticipantsRequest"
            example:
              participants:
                - "+15551234567"
      responses:
        "201":
          description: Per-participant outcome of the promotion.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipantChangeResult"
              example:
                results:
                  - id: 15551234567@s.whatsapp.net
                    status: promoted
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/admins/{pid}:
    delete:
      operationId: demoteGroupAdmin
      tags:
        - groups
      summary: Demote an admin to member
      description: >-
        Takes admin rights away from one participant, who stays in the group as an ordinary member. Only
        admins may demote, and WhatsApp never lets anyone demote the group owner. Demote before removing when
        you need to eject an admin, because the removal call fails while the target still holds admin rights.
      x-wapito:
        feature: group-admins
        ban_risk: low
        webhooks:
          - groups.participants
        errors:
          - invalid_recipient
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
        - name: pid
          in: path
          required: true
          description: Participant, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "204":
          description: The admin was demoted to member.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/applications:
    get:
      operationId: listGroupApplications
      tags:
        - groups
      summary: List pending join requests
      description: >-
        Returns everyone waiting for admin approval to join a group that has membership approval enabled. Pair
        it with the `groups.participants` webhook event, whose `join_request` action tells you a new
        application arrived, so your moderation workflow reacts immediately instead of polling this endpoint
        on a timer.
      x-wapito:
        feature: group-join-requests
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "200":
          description: Everyone currently waiting for approval.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipantList"
              example:
                participants:
                  - id: 15551234567@s.whatsapp.net
                    phone: "15551234567"
                    lid: 187264518273645@lid
                    name: Dana Whitfield
                    role: member
                    joined_at: null
                count: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/applications/{pid}/approve:
    post:
      operationId: approveGroupApplication
      tags:
        - groups
      summary: Approve a pending join request
      description: >-
        Lets one waiting applicant into the group. The channel must be an admin. Approving is the moment to
        run whatever check your product needs, from a paid-subscriber lookup to a simple allow-list, because
        once someone is in the group removing them is visible to everyone in the chat.
      x-wapito:
        feature: group-join-requests
        ban_risk: medium
        webhooks:
          - groups.participants
        errors:
          - invalid_recipient
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
        - name: pid
          in: path
          required: true
          description: Participant, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "201":
          description: The applicant joined the group.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipant"
              example:
                id: 15551234567@s.whatsapp.net
                phone: "15551234567"
                lid: 187264518273645@lid
                name: Dana Whitfield
                role: member
                joined_at: "2026-08-14T16:02:11.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/applications/{pid}:
    delete:
      operationId: rejectGroupApplication
      tags:
        - groups
      summary: Reject a pending join request
      description: >-
        Declines one waiting applicant, who is removed from the queue and is not notified beyond no longer
        being able to join. They can apply again with the same invite link, so rotate the link as well when
        you are rejecting the same person repeatedly. The channel must be an admin.
      x-wapito:
        feature: group-join-requests
        ban_risk: low
        webhooks:
          - groups.participants
        errors:
          - invalid_recipient
          - not_found
          - forbidden
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
        - name: pid
          in: path
          required: true
          description: Participant, in any accepted recipient form.
          schema:
            type: string
            examples:
              - "+15551234567"
      responses:
        "204":
          description: The request was rejected.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /groups/{id}/leave:
    post:
      operationId: leaveGroup
      tags:
        - groups
      summary: Leave a group
      description: >-
        Removes the linked account from the group. The group carries on without it, and if the channel was the
        only admin WhatsApp promotes someone else at random, which is rarely what you want. Promote a human
        admin before leaving any group your automation created, and remember that leaving cannot be undone
        without a fresh invite.
      x-wapito:
        feature: leave-group
        ban_risk: low
        webhooks:
          - groups
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Group id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "204":
          description: The channel left the group.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /communities:
    post:
      operationId: createCommunity
      tags:
        - communities
      summary: Create a community
      description: >-
        Creates a WhatsApp Community with a name, a description and optionally a set of existing groups to
        link into it. This endpoint is reserved and returns `501 feature_not_available` today because neither
        the GOWS nor the NOWEB engine implements the community protocol. Use ordinary groups, which are fully
        supported, until it lands.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCommunityRequest"
            example:
              name: Acme Customers
              description: All Acme customer groups in one place.
              groups:
                - 120363041234567890@g.us
      responses:
        "201":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Community"
              example:
                id: 120363055443322110@g.us
                name: Acme Customers
                description: All Acme customer groups in one place.
                owner: 15557654321@s.whatsapp.net
                groups_count: 7
                announcement_group_id: 120363055443322111@g.us
                created_at: "2026-07-19T13:40:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    get:
      operationId: listCommunities
      tags:
        - communities
      summary: List communities
      description: >-
        Returns the communities the linked account belongs to. This endpoint is reserved and returns `501
        feature_not_available` today, since no supported engine exposes community membership. Groups that
        happen to belong to a community still appear normally in the group endpoints, so you lose no
        visibility over the chats themselves.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommunityList"
              example:
                communities:
                  - id: 120363055443322110@g.us
                    name: Acme Customers
                    description: All Acme customer groups in one place.
                    owner: 15557654321@s.whatsapp.net
                    groups_count: 7
                    announcement_group_id: 120363055443322111@g.us
                    created_at: "2026-07-19T13:40:00.000Z"
                count: 1
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /communities/{id}:
    get:
      operationId: getCommunity
      tags:
        - communities
      summary: Get a community
      description: >-
        Returns one community with its name, description, owner, announcement group and the number of groups
        linked to it. This endpoint is reserved and returns `501 feature_not_available` today. The contract is
        published in advance so client SDKs and your own integration code can be written against a stable
        shape.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Community"
              example:
                id: 120363055443322110@g.us
                name: Acme Customers
                description: All Acme customer groups in one place.
                owner: 15557654321@s.whatsapp.net
                groups_count: 7
                announcement_group_id: 120363055443322111@g.us
                created_at: "2026-07-19T13:40:00.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: updateCommunity
      tags:
        - communities
      summary: Update a community
      description: >-
        Renames a community or rewrites its description. This endpoint is reserved and returns `501
        feature_not_available` today because community metadata cannot be written by either engine. Editing
        the underlying groups works normally through the group endpoints, which is the practical workaround
        for most integrations.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCommunityRequest"
            example:
              description: Customer groups by region. Announcements only.
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Community"
              example:
                id: 120363055443322110@g.us
                name: Acme Customers
                description: All Acme customer groups in one place.
                owner: 15557654321@s.whatsapp.net
                groups_count: 7
                announcement_group_id: 120363055443322111@g.us
                created_at: "2026-07-19T13:40:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteCommunity
      tags:
        - communities
      summary: Delete a community
      description: >-
        Deactivates a community and unlinks every group from it. This endpoint is reserved and returns `501
        feature_not_available` today, matching the rest of the community surface, because the protocol
        messages involved are not implemented by any supported engine. The member groups would survive
        deactivation as standalone groups.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /communities/{id}/groups:
    get:
      operationId: listCommunityGroups
      tags:
        - communities
      summary: List groups in a community
      description: >-
        Returns the groups linked into a community, including the announcement group. This endpoint is
        reserved and returns `501 feature_not_available` today. Listing the groups the account belongs to
        through the group endpoints gives you the same chats, just without the community relationship between
        them.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupList"
              example:
                groups:
                  - id: 120363041234567890@g.us
                    subject: Acme Launch Team
                    description: Coordination for the Q3 launch. Keep it on topic.
                    owner: 15557654321@s.whatsapp.net
                    created_at: "2026-08-02T09:14:00.000Z"
                    participants_count: 24
                    is_announce: false
                    is_locked: false
                    is_community_announce: false
                    invite_code: HkQ2ZpL9vRtAeYm1
                    picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                    settings:
                      info_admin_only: true
                      messages_admin_only: false
                      member_add_mode: admin_add
                      membership_approval: true
                count: 1
                total: 7
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    post:
      operationId: linkCommunityGroup
      tags:
        - communities
      summary: Link a group into a community
      description: >-
        Attaches an existing group to a community so its members see the community announcements. This
        endpoint is reserved and returns `501 feature_not_available` today because linking is a community
        protocol message no supported engine sends. Create and manage the group itself through the fully
        supported group endpoints in the meantime.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LinkCommunityGroupRequest"
            example:
              group_id: 120363041234567890@g.us
      responses:
        "201":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
              example:
                id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                owner: 15557654321@s.whatsapp.net
                created_at: "2026-08-02T09:14:00.000Z"
                participants_count: 24
                is_announce: false
                is_locked: false
                is_community_announce: false
                invite_code: HkQ2ZpL9vRtAeYm1
                picture: https://pps.whatsapp.net/v/t61.24694-24/group_n.jpg
                settings:
                  info_admin_only: true
                  messages_admin_only: false
                  member_add_mode: admin_add
                  membership_approval: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /communities/{id}/groups/{group_id}:
    delete:
      operationId: unlinkCommunityGroup
      tags:
        - communities
      summary: Unlink a group from a community
      description: >-
        Detaches a group from a community, leaving it as a standalone group. This endpoint is reserved and
        returns `501 feature_not_available` today, for the same reason linking is unavailable. Nothing about
        the group chat itself would change; only its relationship to the community announcement group is
        removed.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
        - name: group_id
          in: path
          required: true
          description: Group to unlink, ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363041234567890@g.us
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /communities/{id}/participants:
    get:
      operationId: listCommunityParticipants
      tags:
        - communities
      summary: List community members
      description: >-
        Returns everyone who is a member of at least one group in the community, de-duplicated across groups.
        This endpoint is reserved and returns `501 feature_not_available` today. Reading the participant list
        of each group separately and merging the results is the supported way to build the same view.
      x-phase: 2
      x-wapito:
        feature: communities
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Community id ending in `@g.us`.
          schema:
            type: string
            examples:
              - 120363055443322110@g.us
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipantList"
              example:
                participants:
                  - id: 15551234567@s.whatsapp.net
                    phone: "15551234567"
                    lid: 187264518273645@lid
                    name: Dana Whitfield
                    role: member
                    joined_at: "2026-08-14T16:02:11.000Z"
                count: 1
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters:
    post:
      operationId: createNewsletter
      tags:
        - newsletters
      summary: Create a WhatsApp Channel
      description: >-
        Creates a WhatsApp Channel, the broadcast surface WhatsApp calls a newsletter at protocol level, with
        a name, description and cover picture. Channels reach unlimited followers without any of them being in
        your contacts, which makes them the right tool for announcements that would otherwise be a mass
        broadcast. The official Cloud API cannot create or post to Channels at all.
      x-wapito:
        feature: create-channel
        ban_risk: medium
        webhooks: []
        errors:
          - invalid_request
          - channel_not_connected
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateNewsletterRequest"
            example:
              name: Acme Release Notes
              description: Every shipped change, once a week.
              picture: https://acme.example/brand/channel-cover.png
      responses:
        "201":
          description: The Channel as WhatsApp created it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Newsletter"
              example:
                id: 120363099887766554@newsletter
                name: Acme Release Notes
                description: Every shipped change, once a week.
                invite_code: 0029VaAbCdEfGhIjKlMnOp
                invite_link: https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMnOp
                picture: https://pps.whatsapp.net/v/t61.24694-24/newsletter_n.jpg
                subscribers_count: 1842
                verified: false
                role: owner
                created_at: "2026-06-11T12:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    get:
      operationId: listNewsletters
      tags:
        - newsletters
      summary: List WhatsApp Channels
      description: >-
        Returns the Channels the linked account owns, administers or follows, optionally filtered by that
        relationship. Use the owner filter to find the Channels you can post to and the subscriber filter to
        audit what the number follows. Follower counts come straight from WhatsApp and are only exposed for
        Channels the account can administer.
      x-wapito:
        feature: create-channel
        ban_risk: low
        webhooks: []
        errors:
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: role
          in: query
          required: false
          description: Filter by the relationship of the account to the Channel.
          schema:
            type: string
            enum:
              - owner
              - admin
              - subscriber
              - guest
            examples:
              - owner
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: A page of Channels.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterList"
              example:
                newsletters:
                  - id: 120363099887766554@newsletter
                    name: Acme Release Notes
                    description: Every shipped change, once a week.
                    invite_code: 0029VaAbCdEfGhIjKlMnOp
                    invite_link: https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMnOp
                    picture: https://pps.whatsapp.net/v/t61.24694-24/newsletter_n.jpg
                    subscribers_count: 1842
                    verified: false
                    role: owner
                    created_at: "2026-06-11T12:00:00.000Z"
                count: 1
                total: 3
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/{id}:
    get:
      operationId: getNewsletter
      tags:
        - newsletters
      summary: Get a WhatsApp Channel
      description: >-
        Returns one Channel with its name, description, invite code, cover picture, verification badge and the
        role the linked account has in it. Read it before posting to confirm the account is still an owner or
        admin, because losing that role is the usual reason a previously working post starts failing with
        `forbidden`.
      x-wapito:
        feature: create-channel
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp Channel id ending in `@newsletter`.
          schema:
            type: string
            examples:
              - 120363099887766554@newsletter
      responses:
        "200":
          description: The Channel.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Newsletter"
              example:
                id: 120363099887766554@newsletter
                name: Acme Release Notes
                description: Every shipped change, once a week.
                invite_code: 0029VaAbCdEfGhIjKlMnOp
                invite_link: https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMnOp
                picture: https://pps.whatsapp.net/v/t61.24694-24/newsletter_n.jpg
                subscribers_count: 1842
                verified: false
                role: owner
                created_at: "2026-06-11T12:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteNewsletter
      tags:
        - newsletters
      summary: Delete a WhatsApp Channel
      description: >-
        Permanently deletes a Channel the linked account owns, together with its posts and follower list.
        There is no recovery and followers are not notified, they simply stop seeing it. Transfer ownership by
        promoting another admin instead when the goal is to hand the Channel over rather than end it. Only the
        NOWEB engine implements deletion; on GOWS the call answers `501 engine_unsupported_feature` with a
        hint to switch the channel's engine.
      x-wapito:
        feature: create-channel
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - not_found
          - forbidden
          - engine_unsupported_feature
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp Channel id ending in `@newsletter`.
          schema:
            type: string
            examples:
              - 120363099887766554@newsletter
      responses:
        "204":
          description: The Channel was deleted.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/invite/{code}:
    get:
      operationId: getNewsletterByInvite
      tags:
        - newsletters
      summary: Look up a Channel by invite code
      description: >-
        Resolves the tail of a `whatsapp.com/channel/…` link into the Channel behind it, without following it.
        Use it to validate a link a user pasted, or to show a preview with the name, description and follower
        count before asking them to confirm. An unknown or revoked code resolves to `not_found`.
      x-wapito:
        feature: follow-channels
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: code
          in: path
          required: true
          description: Invite code, the tail of a `whatsapp.com/channel/…` link.
          schema:
            type: string
            examples:
              - 0029VaAbCdEfGhIjKlMnOp
      responses:
        "200":
          description: The Channel behind the code.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Newsletter"
              example:
                id: 120363099887766554@newsletter
                name: Acme Release Notes
                description: Every shipped change, once a week.
                invite_code: 0029VaAbCdEfGhIjKlMnOp
                invite_link: https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMnOp
                picture: https://pps.whatsapp.net/v/t61.24694-24/newsletter_n.jpg
                subscribers_count: 1842
                verified: false
                role: owner
                created_at: "2026-06-11T12:00:00.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/invite/{code}/messages:
    get:
      operationId: previewNewsletterMessages
      tags:
        - newsletters
      summary: Preview a Channel's posts by invite code
      description: >-
        Returns the public preview window of a Channel's recent posts from the tail of a
        `whatsapp.com/channel/…` link, without following the Channel. It is the same window WhatsApp shows
        when a link is opened, newest first, in the message shape the rest of the API uses. There is no
        offset: the preview is a window, not a history.
      x-wapito:
        feature: follow-channels
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: code
          in: path
          required: true
          description: Invite code, the tail of a `whatsapp.com/channel/…` link.
          schema:
            type: string
            examples:
              - 0029VaAbCdEfGhIjKlMnOp
        - name: count
          in: query
          required: false
          description: Window size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
      responses:
        "200":
          description: The Channel's recent posts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageList"
              example:
                messages:
                  - id: false_120363099887766554@newsletter_9F31A0C4D7E2B6081A55
                    chat_id: 120363099887766554@newsletter
                    from_me: false
                    from: 120363099887766554@newsletter
                    from_lid: null
                    from_name: null
                    participant: null
                    type: text
                    timestamp: 1789459321000
                    source: app
                    text:
                      body: Release 4.2 is out. Notes in the pinned post.
                    status: delivered
                count: 1
                total: null
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/find:
    get:
      operationId: findNewsletters
      tags:
        - newsletters
      summary: Search public WhatsApp Channels
      description: >-
        Searches the public Channel directory by text, or lists the most viewed Channels for a country when no
        query is given. Results carry enough detail to show a picker in your own interface. Searching does not
        follow anything; pass an id from the results to the subscribe endpoint when the user chooses one.
      x-wapito:
        feature: follow-channels
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: q
          in: query
          required: false
          description: Free-text search over Channel names and descriptions.
          schema:
            type: string
            examples:
              - release notes
        - name: country
          in: query
          required: false
          description: ISO 3166-1 alpha-2 country code to rank popular Channels for.
          schema:
            type: string
            examples:
              - GB
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
      responses:
        "200":
          description: Matching public Channels.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterList"
              example:
                newsletters:
                  - id: 120363099887766554@newsletter
                    name: Acme Release Notes
                    description: Every shipped change, once a week.
                    invite_code: 0029VaAbCdEfGhIjKlMnOp
                    invite_link: https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMnOp
                    picture: https://pps.whatsapp.net/v/t61.24694-24/newsletter_n.jpg
                    subscribers_count: 1842
                    verified: false
                    role: guest
                    created_at: "2026-06-11T12:00:00.000Z"
                count: 1
                total: 1
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/{id}/messages:
    get:
      operationId: listNewsletterMessages
      tags:
        - newsletters
      summary: List posts in a WhatsApp Channel
      description: >-
        Returns a page of posts from a Channel, newest first, in the same message shape the rest of the API
        uses. On the GOWS engine this is the preview window WhatsApp serves for the Channel (`offset` pages
        inside that window); on the NOWEB engine it is the engine's own message store, which needs engine
        storage enabled, otherwise the list comes back empty. A malformed Channel id is `invalid_request`.
      x-wapito:
        feature: follow-channels
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: partial
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp Channel id ending in `@newsletter`.
          schema:
            type: string
            examples:
              - 120363099887766554@newsletter
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: A page of Channel posts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageList"
              example:
                messages:
                  - id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                    chat_id: 120363099887766554@newsletter
                    from_me: false
                    from: "15551234567"
                    from_lid: 187264518273645@lid
                    from_name: Dana Whitfield
                    participant: null
                    type: image
                    timestamp: 1789459321000
                    source: app
                    image:
                      id: med_01JRQ8F4X9N2K7YB3C5V6W8H0T
                      link: >-
                        https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35
                      mime_type: image/jpeg
                      file_size: 184213
                      file_name: receipt.jpg
                      caption: Here is the receipt you asked for
                    context:
                      quoted_id: null
                      quoted_author: null
                      forwarded: false
                      mentions: []
                    status: delivered
                count: 1
                total: 42
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/{id}/subscribe:
    post:
      operationId: subscribeToNewsletter
      tags:
        - newsletters
      summary: Follow a WhatsApp Channel
      description: >-
        Makes the linked account follow a Channel so its posts arrive as ordinary `messages` events. This
        endpoint is reserved and returns `501 feature_not_available` today, because following is only
        implemented by the WEBJS engine which Wapito does not run. Reading a Channel by invite code still
        works through the lookup endpoint.
      x-phase: 2
      x-wapito:
        feature: follow-channels
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp Channel id ending in `@newsletter`.
          schema:
            type: string
            examples:
              - 120363099887766554@newsletter
      responses:
        "201":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Newsletter"
              example:
                id: 120363099887766554@newsletter
                name: Acme Release Notes
                description: Every shipped change, once a week.
                invite_code: 0029VaAbCdEfGhIjKlMnOp
                invite_link: https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMnOp
                picture: https://pps.whatsapp.net/v/t61.24694-24/newsletter_n.jpg
                subscribers_count: 1842
                verified: false
                role: subscriber
                created_at: "2026-06-11T12:00:00.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: unsubscribeFromNewsletter
      tags:
        - newsletters
      summary: Unfollow a WhatsApp Channel
      description: >-
        Stops the linked account following a Channel, so its posts no longer arrive. This endpoint is reserved
        and returns `501 feature_not_available` today for the same reason following is unavailable: neither
        supported engine implements the subscription protocol. Filter unwanted Channel posts in your webhook
        handler as an interim measure.
      x-phase: 2
      x-wapito:
        feature: follow-channels
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp Channel id ending in `@newsletter`.
          schema:
            type: string
            examples:
              - 120363099887766554@newsletter
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /newsletters/{id}/admins:
    post:
      operationId: inviteNewsletterAdmin
      tags:
        - newsletters
      summary: Invite an admin to a Channel
      description: >-
        Invites another WhatsApp account to co-administer a Channel the linked account owns. This endpoint is
        reserved and returns `501 feature_not_available` today because admin invitations are not implemented
        by the engines Wapito runs. Until then, admins have to be invited from the WhatsApp app on the linked
        phone.
      x-phase: 2
      x-wapito:
        feature: create-channel
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp Channel id ending in `@newsletter`.
          schema:
            type: string
            examples:
              - 120363099887766554@newsletter
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupParticipantsRequest"
            example:
              participants:
                - "+15551234567"
      responses:
        "201":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupParticipantChangeResult"
              example:
                results:
                  - id: 15551234567@s.whatsapp.net
                    status: promoted
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /stories/text:
    post:
      operationId: postTextStory
      tags:
        - stories
      summary: Post a text status
      description: >-
        Posts a coloured text status visible for twenty-four hours to the contacts you name, or to every known
        contact when you name none. Statuses are seen by people who already have your number saved, which
        makes them a softer channel than a broadcast: nobody receives a notification they did not opt into by
        saving you.
      x-wapito:
        feature: post-status
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_request
          - invalid_recipient
          - channel_not_connected
          - quota_exceeded
          - warmup_limit
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: partial
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TextStoryRequest"
            example:
              body: Workshop closed Friday for stocktake. Orders ship Monday.
              background_color: "#0B7F5C"
              font: 2
              contacts:
                - "+15551234567"
                - "+15559876543"
      responses:
        "201":
          description: The status that was posted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Story"
              example:
                id: true_status@broadcast_A71D3F9C0B2E5468
                type: text
                timestamp: 1789459500000
                link: null
                caption: Workshop closed Friday for stocktake. Orders ship Monday.
                contacts:
                  - "15551234567"
                  - "15559876543"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /stories/media:
    post:
      operationId: postMediaStory
      tags:
        - stories
      summary: Post an image or video status
      description: >-
        Posts an image or video status with an optional caption, visible for twenty-four hours. Video statuses
        are capped by WhatsApp at thirty seconds and are re-encoded on the recipient device, so keep them
        short and legible on a phone screen. Supply the file as a URL, base64 bytes or a Wapito media id.
      x-wapito:
        feature: post-status
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_request
          - invalid_recipient
          - payload_too_large
          - unsupported_media_type
          - channel_not_connected
          - quota_exceeded
          - warmup_limit
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: partial
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MediaStoryRequest"
            example:
              media: https://acme.example/status/new-colours.jpg
              caption: New colours, same price.
              contacts:
                - "+15551234567"
      responses:
        "201":
          description: The status that was posted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Story"
              example:
                id: true_status@broadcast_A71D3F9C0B2E5468
                type: image
                timestamp: 1789459500000
                link: >-
                  https://api.wapito.com/v1/media/med_01JRQ9AAB2C3D4E5F6G7H8J9K0?exp=1790064500&sig=1b7e4c02da96f358
                caption: New colours, same price.
                contacts:
                  - "15551234567"
                  - "15559876543"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /stories/audio:
    post:
      operationId: postAudioStory
      tags:
        - stories
      summary: Post a voice status
      description: >-
        Posts a voice status: an audio clip shown on a coloured background, which WhatsApp treats like a voice
        note in the status tray. Wapito converts your input to the Opus format WhatsApp requires. Voice
        statuses cut through better than text for short personal updates, and like every status they expire
        after twenty-four hours.
      x-wapito:
        feature: post-status
        ban_risk: medium
        webhooks:
          - messages
          - messages.status
        errors:
          - invalid_request
          - invalid_recipient
          - payload_too_large
          - unsupported_media_type
          - channel_not_connected
          - quota_exceeded
          - warmup_limit
        former_ids: []
        quota: sent
        engines:
          gows: supported
          noweb: partial
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AudioStoryRequest"
            example:
              media: https://acme.example/status/monday-update.m4a
              background_color: "#1D2B3A"
      responses:
        "201":
          description: The status that was posted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Story"
              example:
                id: true_status@broadcast_A71D3F9C0B2E5468
                type: audio
                timestamp: 1789459500000
                link: >-
                  https://api.wapito.com/v1/media/med_01JRQ9AAB2C3D4E5F6G7H8J9K0?exp=1790064500&sig=1b7e4c02da96f358
                caption: null
                contacts:
                  - "15551234567"
                  - "15559876543"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /stories:
    get:
      operationId: listStories
      tags:
        - stories
      summary: List statuses
      description: >-
        Returns the statuses currently visible to the linked account, its own and those of its contacts. This
        endpoint is reserved and returns `501 feature_not_available` today because neither engine exposes the
        status tray. Your own posted statuses are still returned by the endpoints that create them, so record
        those ids if you need them.
      x-phase: 2
      x-wapito:
        feature: stories-manage
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryList"
              example:
                stories:
                  - id: true_status@broadcast_A71D3F9C0B2E5468
                    type: image
                    timestamp: 1789459500000
                    link: >-
                      https://api.wapito.com/v1/media/med_01JRQ9AAB2C3D4E5F6G7H8J9K0?exp=1790064500&sig=1b7e4c02da96f358
                    caption: New colours, same price.
                    contacts:
                      - "15551234567"
                      - "15559876543"
                count: 1
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /stories/{id}:
    delete:
      operationId: deleteStory
      tags:
        - stories
      summary: Delete a status
      description: >-
        Removes a status before its twenty-four hour lifetime ends. This endpoint is reserved and returns `501
        feature_not_available` today because status deletion is not implemented by either engine. Statuses
        expire on their own, so the practical mitigation for a mistaken post is to publish a correction
        immediately.
      x-phase: 2
      x-wapito:
        feature: stories-manage
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Status message id returned when the status was posted.
          schema:
            type: string
            examples:
              - true_status@broadcast_A71D3F9C0B2E5468
      responses:
        "204":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /labels:
    get:
      operationId: listLabels
      tags:
        - labels
      summary: List labels
      description: >-
        Returns every label defined on the linked WhatsApp Business account, including the presets WhatsApp
        ships such as New customer and Paid. Labels only exist on Business accounts: a personal number returns
        `business_account_required`, which is the fastest way to detect the account type before you build a
        workflow on top of labels.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks: []
        errors:
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: Every label on the account.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LabelList"
              example:
                labels:
                  - id: "6"
                    name: Paid
                    color: 6
                    color_hex: "#00A5F4"
                    predefined_id: null
                count: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    post:
      operationId: createLabel
      tags:
        - labels
      summary: Create a label
      description: >-
        Creates a new label with a name and one of the twenty WhatsApp colour slots. WhatsApp caps the number
        of labels per Business account, so reuse labels rather than minting one per campaign. The created
        label is immediately available on the linked phone as well, which keeps human operators and
        automations working from the same taxonomy.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks:
          - labels
        errors:
          - invalid_request
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLabelRequest"
            example:
              name: Awaiting payment
              color: 4
      responses:
        "201":
          description: The label that was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Label"
              example:
                id: "11"
                name: Awaiting payment
                color: 4
                color_hex: "#E542A3"
                predefined_id: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /labels/{id}:
    patch:
      operationId: updateLabel
      tags:
        - labels
      summary: Rename or recolour a label
      description: >-
        Changes the name or colour of an existing label. Every chat already carrying the label keeps it, so
        renaming is safe and is much better than deleting and recreating, which would silently detach the
        label from every conversation it was attached to. Preset WhatsApp labels can be renamed like any
        other.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks:
          - labels
        errors:
          - invalid_request
          - not_found
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Label id as assigned by WhatsApp.
          schema:
            type: string
            examples:
              - "6"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateLabelRequest"
            example:
              name: Paid in full
      responses:
        "200":
          description: The label after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Label"
              example:
                id: "6"
                name: Paid in full
                color: 6
                color_hex: "#00A5F4"
                predefined_id: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: deleteLabel
      tags:
        - labels
      summary: Delete a label
      description: >-
        Deletes a label and detaches it from every chat that carried it. The chats themselves are untouched.
        This cannot be undone and the association list is not recoverable afterwards, so export the
        associations first if they encode anything your pipeline depends on.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks:
          - labels
        errors:
          - not_found
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Label id as assigned by WhatsApp.
          schema:
            type: string
            examples:
              - "6"
      responses:
        "204":
          description: The label was deleted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /labels/{id}/associations:
    get:
      operationId: listLabelAssociations
      tags:
        - labels
      summary: List chats carrying a label
      description: >-
        Returns the chats a label is attached to, which is how you turn a label into a working segment: read
        the associations, then send to each chat through the message endpoints while respecting the anti-ban
        guards. Associations follow the chat, so a labelled conversation keeps its label across reconnects.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Label id as assigned by WhatsApp.
          schema:
            type: string
            examples:
              - "6"
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Chats carrying the label.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatList"
              example:
                chats:
                  - id: 15551234567@s.whatsapp.net
                    name: Dana Whitfield
                    type: individual
                    unread_count: 2
                    archived: false
                    pinned: false
                    muted_until: null
                    timestamp: 1789459321000
                    picture: https://pps.whatsapp.net/v/t61.24694-24/example_n.jpg
                    last_message: null
                count: 1
                total: 18
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    post:
      operationId: addLabelAssociation
      tags:
        - labels
      summary: Attach a label to a chat
      description: >-
        Attaches a label to one conversation, exactly as tapping it in WhatsApp Business would. Attaching an
        already attached label is a no-op rather than an error, which makes the call safe to run from an
        idempotent workflow. The change is mirrored to your webhooks as a `labels` event.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks:
          - labels
        errors:
          - invalid_request
          - not_found
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Label id as assigned by WhatsApp.
          schema:
            type: string
            examples:
              - "6"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelAssociationRequest"
            example:
              chat_id: "+15551234567"
      responses:
        "201":
          description: The label was attached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Acknowledgement"
              example:
                ok: true
                id: "6"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    delete:
      operationId: removeLabelAssociation
      tags:
        - labels
      summary: Detach a label from a chat
      description: >-
        Removes a label from one conversation. The label itself and all its other associations survive. As
        with attaching, detaching something that is not attached is a no-op rather than an error, so a
        reconciliation job can call it freely without checking the current state first.
      x-wapito:
        feature: labels
        ban_risk: low
        webhooks:
          - labels
        errors:
          - invalid_request
          - not_found
          - business_account_required
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Label id as assigned by WhatsApp.
          schema:
            type: string
            examples:
              - "6"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelAssociationRequest"
            example:
              chat_id: "+15551234567"
      responses:
        "204":
          description: The label was detached.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /business/profile:
    get:
      operationId: getBusinessProfile
      tags:
        - labels
      summary: Get the business profile
      description: >-
        Returns the public business profile of the linked account: category, description, email, websites,
        address and opening hours. This endpoint is reserved and returns `501 feature_not_available` today
        because no supported engine exposes the business profile surface. The account name and picture are
        available through the profile endpoints in the meantime.
      x-phase: 2
      x-wapito:
        feature: business-catalog
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BusinessProfile"
              example:
                id: 15557654321@s.whatsapp.net
                name: Acme Supplies
                category: Retail
                description: Workshop tools shipped next day.
                email: hello@acme.example
                website:
                  - https://acme.example
                address: 14 Forge Lane, Sheffield
                latitude: 53.3811
                longitude: -1.4701
                business_hours: Mon-Fri 09:00-17:30
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    patch:
      operationId: updateBusinessProfile
      tags:
        - labels
      summary: Update the business profile
      description: >-
        Changes the description, category, email, websites or address on the business profile. This endpoint
        is reserved and returns `501 feature_not_available` today, matching the read side, because the
        business profile is not writable through either engine. Edit it from WhatsApp Business on the linked
        phone for now.
      x-phase: 2
      x-wapito:
        feature: business-catalog
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateBusinessProfileRequest"
            example:
              description: Workshop tools shipped next day.
              email: hello@acme.example
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BusinessProfile"
              example:
                id: 15557654321@s.whatsapp.net
                name: Acme Supplies
                category: Retail
                description: Workshop tools shipped next day.
                email: hello@acme.example
                website:
                  - https://acme.example
                address: 14 Forge Lane, Sheffield
                latitude: 53.3811
                longitude: -1.4701
                business_hours: Mon-Fri 09:00-17:30
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /business/catalog:
    get:
      operationId: listBusinessCatalog
      tags:
        - labels
      summary: List catalog products
      description: >-
        Returns the products in the WhatsApp Business catalog of the linked account, with prices, availability
        and images. This endpoint is reserved and returns `501 feature_not_available` today because catalog
        access is not implemented by any supported engine. Sending product links as ordinary messages is the
        supported alternative.
      x-phase: 2
      x-wapito:
        feature: business-catalog
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogProductList"
              example:
                products:
                  - id: "7381920456123789"
                    retailer_id: SKU-4182
                    name: Bench vice 100 mm
                    description: Cast iron bench vice with swivel base.
                    price: 4900
                    currency: GBP
                    availability: in stock
                    url: https://acme.example/p/bench-vice-100
                    images:
                      - https://acme.example/img/bench-vice-100.jpg
                count: 1
                total: 48
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /business/catalog/{id}:
    get:
      operationId: getBusinessProduct
      tags:
        - labels
      summary: Get a catalog product
      description: >-
        Returns one product from the WhatsApp Business catalog by its WhatsApp product id. This endpoint is
        reserved and returns `501 feature_not_available` today, for the same reason the catalog listing is
        unavailable. The contract is published now so client SDKs will not need a breaking change when engine
        support arrives.
      x-phase: 2
      x-wapito:
        feature: business-catalog
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: WhatsApp product id.
          schema:
            type: string
            examples:
              - "7381920456123789"
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogProduct"
              example:
                id: "7381920456123789"
                retailer_id: SKU-4182
                name: Bench vice 100 mm
                description: Cast iron bench vice with swivel base.
                price: 4900
                currency: GBP
                availability: in stock
                url: https://acme.example/p/bench-vice-100
                images:
                  - https://acme.example/img/bench-vice-100.jpg
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /business/orders:
    get:
      operationId: listBusinessOrders
      tags:
        - labels
      summary: List catalog orders
      description: >-
        Returns the carts customers have submitted from the catalog, with line items and totals. This endpoint
        is reserved and returns `501 feature_not_available` today because order messages are not decoded by
        either engine. Order messages still arrive in the `messages` webhook stream, where you can parse them
        yourself if you need them.
      x-phase: 2
      x-wapito:
        feature: business-catalog
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: count
          in: query
          required: false
          description: Page size, 1-500. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
            examples:
              - 50
        - name: offset
          in: query
          required: false
          description: Items to skip before the first returned item. Use it with `count` to page.
          schema:
            type: integer
            minimum: 0
            default: 0
            examples:
              - 0
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BusinessOrderList"
              example:
                orders:
                  - id: "5512338877665544"
                    chat_id: 15551234567@s.whatsapp.net
                    token: AR2Gh8Kd0PqZ
                    status: pending
                    currency: GBP
                    subtotal: 9800
                    total: 9800
                    items:
                      - product_id: "7381920456123789"
                        name: Bench vice 100 mm
                        quantity: 2
                        price: 4900
                    created_at: "2026-09-15T06:58:12.000Z"
                count: 1
                total: 9
                offset: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /business/orders/{id}:
    get:
      operationId: getBusinessOrder
      tags:
        - labels
      summary: Get a catalog order
      description: >-
        Returns one submitted cart with its items, quantities, currency and totals. This endpoint is reserved
        and returns `501 feature_not_available` today, matching the rest of the business surface. Until it
        lands, treat the raw order message delivered to your webhook as the source of truth for what the
        customer asked for.
      x-phase: 2
      x-wapito:
        feature: business-catalog
        ban_risk: low
        webhooks: []
        errors:
          - feature_not_available
        former_ids: []
        quota: req
        engines:
          gows: "no"
          noweb: "no"
      parameters:
        - name: id
          in: path
          required: true
          description: Order id as delivered in the order message.
          schema:
            type: string
            examples:
              - "5512338877665544"
      responses:
        "200":
          description: Reserved. Returns `501 feature_not_available` until engine support exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BusinessOrder"
              example:
                id: "5512338877665544"
                chat_id: 15551234567@s.whatsapp.net
                token: AR2Gh8Kd0PqZ
                status: pending
                currency: GBP
                subtotal: 9800
                total: 9800
                items:
                  - product_id: "7381920456123789"
                    name: Bench vice 100 mm
                    quantity: 2
                    price: 4900
                created_at: "2026-09-15T06:58:12.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /media:
    post:
      operationId: uploadMedia
      tags:
        - media
      summary: Upload a file
      description: >-
        Uploads a file to Wapito storage and returns a media id you can reuse across sends, which is the right
        pattern when the same image or document goes to many recipients. The response also carries a signed
        link your own systems can fetch. Files larger than the plan cap are rejected with `payload_too_large`
        before anything is stored.
      x-wapito:
        feature: media
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - payload_too_large
          - unsupported_media_type
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        description: Either a multipart form with the file part and an optional file name, or a JSON body carrying the
          file as a `data:` URI.
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/UploadMediaRequest"
            example:
              file: <raw file bytes of Invoice-4182.pdf>
              filename: Invoice-4182.pdf
          application/json:
            schema:
              $ref: "#/components/schemas/UploadMediaJsonRequest"
            example:
              data: data:application/pdf;base64,JVBERi0xLjQK…
              filename: Invoice-4182.pdf
      responses:
        "201":
          description: The stored file.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaFile"
              example:
                id: med_01JRQ8F4X9N2K7YB3C5V6W8H0T
                channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
                message_id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                mimetype: image/jpeg
                size_bytes: 184213
                filename: receipt.jpg
                link: >-
                  https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35
                created_at: "2026-09-15T07:22:01.000Z"
                expires_at: "2026-09-22T07:22:01.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
  /media/{id}:
    get:
      operationId: getMedia
      tags:
        - media
      summary: Download a stored file
      description: >-
        Streams the bytes of a stored file. Two ways in are accepted: your channel token as a Bearer header,
        or the `exp` and `sig` query parameters from a signed link, which is what makes the `link` on inbound
        media safe to hand to a browser or a downstream service. Signed links expire after twenty-four hours
        on sandbox and seven days on premium.
      x-wapito:
        feature: media
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - forbidden
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      security:
        - bearerAuth: []
        - {}
      parameters:
        - name: id
          in: path
          required: true
          description: Media id (`med_` + ULID).
          schema:
            type: string
            examples:
              - med_01JRQ8F4X9N2K7YB3C5V6W8H0T
        - name: exp
          in: query
          required: false
          description: Expiry timestamp from a signed link, in Unix seconds.
          schema:
            type: string
            examples:
              - "1790064121"
        - name: sig
          in: query
          required: false
          description: HMAC signature from a signed link.
          schema:
            type: string
            examples:
              - 8f2c1d94b6a70e35
      responses:
        "200":
          description: The file bytes, with the stored media type.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      operationId: deleteMedia
      tags:
        - media
      summary: Delete a stored file
      description: >-
        Deletes a stored file immediately instead of waiting for its retention window to end, and invalidates
        every signed link pointing at it. Messages already delivered to WhatsApp keep their copy; this only
        removes the file from Wapito storage. Use it to honour a deletion request from one of your own users.
      x-wapito:
        feature: media
        ban_risk: low
        webhooks: []
        errors:
          - not_found
          - forbidden
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Media id (`med_` + ULID).
          schema:
            type: string
            examples:
              - med_01JRQ8F4X9N2K7YB3C5V6W8H0T
      responses:
        "204":
          description: The file was deleted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
  /messages/{id}/media:
    get:
      operationId: getMessageMedia
      tags:
        - media
      summary: Get the media of a message
      description: >-
        Returns the stored file attached to a message, with a fresh signed link. Wapito copies inbound media
        out of the engine within the hour the engine keeps it, so this works long after WhatsApp itself has
        discarded the file. A message whose media copy failed reports `not_found` rather than returning a
        broken link.
      x-wapito:
        feature: media
        ban_risk: low
        webhooks: []
        errors:
          - message_not_found
          - not_found
          - channel_not_connected
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Message id carrying the media.
          schema:
            type: string
            examples:
              - false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
      responses:
        "200":
          description: "With `Accept: application/json`: the stored file record and a fresh signed link."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaFile"
              example:
                id: med_01JRQ8F4X9N2K7YB3C5V6W8H0T
                channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
                message_id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                mimetype: image/jpeg
                size_bytes: 184213
                filename: receipt.jpg
                link: >-
                  https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35
                created_at: "2026-09-15T07:22:01.000Z"
                expires_at: "2026-09-22T07:22:01.000Z"
        "302":
          description: "Without `Accept: application/json` (a browser following the link): a redirect to the fresh
            signed link, so the caller gets the bytes."
          headers:
            Location:
              description: The signed `GET /media/{id}?exp&sig` link.
              schema:
                type: string
                format: uri
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
  /webhooks:
    get:
      operationId: listWebhooks
      tags:
        - webhooks
      summary: List webhooks
      description: >-
        Returns the webhook subscriptions configured for this channel, with their URLs, event filters, custom
        headers and a masked preview of each signing secret. Secrets are never returned in full after
        creation. Sandbox channels may have two webhooks and premium channels five, which the channel limits
        endpoint also reports.
      x-wapito:
        feature: webhooks
        ban_risk: low
        webhooks: []
        errors:
          - channel_locked
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      responses:
        "200":
          description: Every webhook on the channel.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookList"
              example:
                webhooks:
                  - id: whk_01JRQ8F4X9N2K7YB3C5V6W8H0T
                    url: https://hooks.acme.example/wapito
                    events:
                      - messages
                      - messages.status
                      - groups.participants
                    enabled: true
                    headers:
                      X-Acme-Tenant: eu-1
                    secret_preview: whsec_…f31a
                    created_at: "2026-09-01T08:30:00.000Z"
                count: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: createWebhook
      tags:
        - webhooks
      summary: Create a webhook
      description: >-
        Subscribes an HTTPS endpoint to a set of events. Event names may be exact, prefix wildcards such as
        `messages.*`, or `*` for everything. The signing secret is returned exactly once in this response, so
        store it immediately; every delivery is signed with it in the `X-Wapito-Signature` header. URLs that
        resolve to private or loopback addresses are rejected.
      x-wapito:
        feature: webhooks
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - webhook_url_invalid
          - plan_required
          - quota_exceeded
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookRequest"
            example:
              url: https://hooks.acme.example/wapito
              events:
                - messages
                - messages.status
                - groups.participants
              headers:
                X-Acme-Tenant: eu-1
      responses:
        "201":
          description: >-
            The webhook, including its signing secret. This is the only time the full secret is returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreatedWebhook"
              example:
                id: whk_01JRQ8F4X9N2K7YB3C5V6W8H0T
                url: https://hooks.acme.example/wapito
                events:
                  - messages
                  - messages.status
                  - groups.participants
                enabled: true
                headers:
                  X-Acme-Tenant: eu-1
                secret_preview: whsec_…f31a
                created_at: "2026-09-01T08:30:00.000Z"
                secret: whsec_8f2c1d94b6a70e35c4d1a2b3e4f5061728394a5b6c7d8e9f
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
  /webhooks/{id}:
    patch:
      operationId: updateWebhook
      tags:
        - webhooks
      summary: Update a webhook
      description: >-
        Changes the URL, event filter, headers or enabled flag of an existing webhook. Only the fields you
        send are changed; the signing secret is fixed for the life of the webhook (delete and create one to
        get a new secret). Disabling a webhook stops new deliveries from being queued, which is the safe way
        to pause an endpoint you are redeploying rather than letting retries pile up.
      x-wapito:
        feature: webhooks
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - not_found
          - webhook_url_invalid
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Webhook id (`whk_` + ULID).
          schema:
            type: string
            examples:
              - whk_01JRQ8F4X9N2K7YB3C5V6W8H0T
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWebhookRequest"
            example:
              events:
                - messages
                - messages.status
                - channel
              enabled: true
      responses:
        "200":
          description: The webhook after the change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
              example:
                id: whk_01JRQ8F4X9N2K7YB3C5V6W8H0T
                url: https://hooks.acme.example/wapito
                events:
                  - messages
                  - messages.status
                  - groups.participants
                enabled: true
                headers:
                  X-Acme-Tenant: eu-1
                secret_preview: whsec_…f31a
                created_at: "2026-09-01T08:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      operationId: deleteWebhook
      tags:
        - webhooks
      summary: Delete a webhook
      description: >-
        Removes a webhook subscription and cancels every delivery job still pending for it. Events produced
        afterwards are simply not queued for this endpoint. Disable the webhook instead when you only want to
        pause deliveries, because deleting loses the configuration and the signing secret for good.
      x-wapito:
        feature: webhooks
        ban_risk: low
        webhooks: []
        errors:
          - not_found
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Webhook id (`whk_` + ULID).
          schema:
            type: string
            examples:
              - whk_01JRQ8F4X9N2K7YB3C5V6W8H0T
      responses:
        "204":
          description: The webhook was deleted and its pending jobs cancelled.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
  /webhooks/{id}/test:
    post:
      operationId: testWebhook
      tags:
        - webhooks
      summary: Send a test delivery
      description: >-
        Posts a synthetic event of the kind you name to the webhook and reports what your endpoint answered:
        status code, round-trip time and the first part of the response body, plus the exact signature header
        that was sent so you can replay the verification locally. Nothing is queued and nothing is retried,
        which makes this safe to call while debugging.
      x-wapito:
        feature: webhooks
        ban_risk: low
        webhooks: []
        errors:
          - invalid_request
          - not_found
        former_ids: []
        quota: req
        engines:
          gows: supported
          noweb: supported
      parameters:
        - name: id
          in: path
          required: true
          description: Webhook id (`whk_` + ULID).
          schema:
            type: string
            examples:
              - whk_01JRQ8F4X9N2K7YB3C5V6W8H0T
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestWebhookRequest"
            example:
              event: messages
      responses:
        "200":
          description: What your endpoint answered.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTestResult"
              example:
                delivered: true
                status_code: 200
                duration_ms: 148
                error: null
                response_excerpt: '{"ok":true}'
                signature: t=1789459200123,v1=5f8a1c0b7d2e4936ab55c1e7f04d9b83c6a2ef10d4b79325c8f6a0d1e2b3c4d5
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"
webhooks:
  messages:
    post:
      operationId: onMessages
      tags:
        - webhooks
      summary: Message sent or received
      description: >-
        Fired for every message on the channel, inbound and outbound, in direct chats, groups and WhatsApp
        Channels. It is derived from the engine `message.any` stream rather than `message`, so a message you
        send through the API arrives exactly once with `from_me: true` and `source: api`. Media is already
        copied into Wapito storage and `link` is a signed URL you can fetch with your channel token.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: messages
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                chat_id: 15551234567@s.whatsapp.net
                from_me: false
                from: "15551234567"
                from_lid: 187264518273645@lid
                from_name: Dana Whitfield
                participant: null
                type: image
                timestamp: 1789459321000
                source: app
                image:
                  id: med_01JRQ8F4X9N2K7YB3C5V6W8H0T
                  link: >-
                    https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35
                  mime_type: image/jpeg
                  file_size: 184213
                  file_name: receipt.jpg
                  caption: Here is the receipt you asked for
                context:
                  quoted_id: null
                  quoted_author: null
                  forwarded: false
                  mentions: []
                status: delivered
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  messages.edited:
    post:
      operationId: onMessagesEdited
      tags:
        - webhooks
      summary: Message text edited
      description: >-
        Fired when the author edits a message that already exists in the chat, whether that author is the
        linked phone, your API integration or the person on the other side. Only the new body is delivered, so
        keep your own copy of the previous text if you need an audit trail. WhatsApp allows edits for a
        limited window after sending.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesEditedEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: messages.edited
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                message_id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                body: "Your order #4182 has shipped. Tracking: https://acme.example/t/4182"
                timestamp: 1789459360000
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  messages.deleted:
    post:
      operationId: onMessagesDeleted
      tags:
        - webhooks
      summary: Message deleted for everyone
      description: >-
        Fired when a message is revoked for everyone. WhatsApp does not deliver the original content with the
        revocation, so the event carries only identifiers; if you need the deleted text, read it from your own
        store using `message_id`. Deleting only for yourself on the linked phone does not produce this event.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesDeletedEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: messages.deleted
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                message_id: false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55
                chat_id: 15551234567@s.whatsapp.net
                from: "15551234567"
                timestamp: 1789459380000
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  messages.status:
    post:
      operationId: onMessagesStatus
      tags:
        - webhooks
      summary: Delivery receipt for a sent message
      description: >-
        Fired every time the delivery state of an outbound message moves forward: pending, sent, delivered,
        read and, for voice notes, played. Statuses can arrive out of order and a group message emits one
        event per participant, so store the highest state you have seen per recipient instead of overwriting
        blindly. A `failed` status means WhatsApp rejected the message.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesStatusEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: messages.status
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                message_id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                recipient: null
                status: read
                ack: 3
                timestamp: 1789459400000
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  messages.reactions:
    post:
      operationId: onMessagesReactions
      tags:
        - webhooks
      summary: Reaction added or removed
      description: >-
        Fired when anyone adds, changes or removes an emoji reaction on a message in a chat the channel takes
        part in. Removing a reaction arrives as the same event with an empty `emoji`, so a reaction counter
        should upsert on the pair of `message_id` and `from` rather than appending. Reactions are cheap
        signals and carry very little ban risk.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesReactionsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: messages.reactions
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                message_id: true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B
                chat_id: 15551234567@s.whatsapp.net
                from: "15551234567"
                emoji: 👍
                timestamp: 1789459420000
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  chats:
    post:
      operationId: onChats
      tags:
        - webhooks
      summary: Chat archived or unarchived
      description: >-
        Fired when a chat moves into or out of the archive on the linked phone. Only the NOWEB engine reports
        archive changes, so channels on the default GOWS engine never receive this event; switch the channel
        if archive state matters to your product. Other chat mutations such as pinning and muting are not
        reported by either engine.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: chats
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                action: archive
                chat_id: 15551234567@s.whatsapp.net
                archived: true
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  contacts:
    post:
      operationId: onContacts
      tags:
        - webhooks
      summary: Contact discovered or resolved
      description: >-
        Fired when Wapito learns about a contact for the first time, usually on their first inbound message,
        and again when a previously LID-only contact is resolved to a real phone number. Use it to keep your
        CRM in step without polling `GET /contacts`. The event is derived by Wapito rather than emitted by
        WhatsApp, so it is consistent across both engines.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: contacts
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                action: new
                id: 15551234567@s.whatsapp.net
                phone: "15551234567"
                lid: 187264518273645@lid
                name: Dana Whitfield
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  groups:
    post:
      operationId: onGroups
      tags:
        - webhooks
      summary: Group joined, left or updated
      description: >-
        Fired when the channel joins or leaves a group and when group metadata such as the subject,
        description or icon changes. It does not carry membership changes; those arrive as
        `groups.participants`. Pair the two events to keep a local mirror of every group the channel belongs
        to without polling the group endpoints.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: groups
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                action: update
                group_id: 120363041234567890@g.us
                subject: Acme Launch Team
                description: Coordination for the Q3 launch. Keep it on topic.
                picture: null
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  groups.participants:
    post:
      operationId: onGroupsParticipants
      tags:
        - webhooks
      summary: Group membership or role changed
      description: >-
        Fired when participants are added or removed, when someone is promoted to or demoted from admin, and
        when a join request lands in the approval queue of a group with membership approval enabled. This is
        the event to drive moderation bots from: answer a `join_request` action by calling the group
        applications endpoints rather than polling them.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupsParticipantsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: groups.participants
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                group_id: 120363041234567890@g.us
                action: add
                participants:
                  - id: 15551234567@s.whatsapp.net
                    phone: "15551234567"
                    role: member
                by: "15557654321"
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  presences:
    post:
      operationId: onPresences
      tags:
        - webhooks
      summary: Presence changed in a subscribed chat
      description: >-
        Fired when a contact goes online or offline or starts and stops typing in a chat you explicitly
        subscribed to. WhatsApp only streams presence for subscribed chats, so call `POST
        /presence/{chat_id}/subscribe` first and expect nothing otherwise. Contacts who hide their last-seen
        setting send presence transitions without a `last_seen` timestamp.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PresencesEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: presences
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                chat_id: 15551234567@s.whatsapp.net
                presence: typing
                last_seen: 1789459280000
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  polls:
    post:
      operationId: onPolls
      tags:
        - webhooks
      summary: Poll vote cast or changed
      description: >-
        Fired when someone votes in a poll the channel sent, including when they change or clear an existing
        vote. The payload always carries the complete current selection for that voter, so replace their
        previous answer instead of adding to it. A vote that could not be decrypted arrives with `failed:
        true` and an empty selection.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PollsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: polls
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                poll_message_id: true_120363041234567890@g.us_5C71E90AB3F4D26178BB
                chat_id: 120363041234567890@g.us
                voter: "15551234567"
                selected_options:
                  - Tuesday 10:00
                failed: false
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  calls:
    post:
      operationId: onCalls
      tags:
        - webhooks
      summary: Incoming call offered or resolved
      description: >-
        Fired when someone calls the linked number and again when that call is accepted or rejected. Wapito
        cannot answer calls, but you can reject one immediately with `POST /calls/reject` or enable
        `settings.reject_calls` to have every call rejected automatically. Leaving calls ringing unanswered on
        an API-driven number is a common spam signal.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: calls
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                call_id: CALL_9F31A0C4D7E2B6081A55
                from: "15551234567"
                status: received
                is_video: false
                is_group: false
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  labels:
    post:
      operationId: onLabels
      tags:
        - webhooks
      summary: Business label changed
      description: >-
        Fired when a WhatsApp Business label is created, renamed, deleted, or attached to or removed from a
        chat, whether the change came from this API or from the linked phone. Only Business accounts produce
        this event; personal accounts never do. Use it to mirror the label taxonomy into your own pipeline in
        real time.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelsEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: labels
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                action: chat_added
                label:
                  id: "6"
                  name: Paid
                  color: 6
                  color_hex: "#FF9485"
                chat_id: 15551234567@s.whatsapp.net
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
  channel:
    post:
      operationId: onChannel
      tags:
        - webhooks
      summary: Channel connection state changed
      description: >-
        Fired on every connection state transition of the channel, including the one you most need to know
        about: `banned`, when WhatsApp blocks the linked number. Alert a human on `banned`, `logged_out` and
        any `disconnected` that lasts more than a few minutes, because no other event will arrive while the
        channel is down.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookEventHeader"
        - $ref: "#/components/parameters/WebhookDeliveryHeader"
        - $ref: "#/components/parameters/WebhookChannelHeader"
        - $ref: "#/components/parameters/WebhookSignatureHeader"
      requestBody:
        required: true
        description: The event Wapito posts to your endpoint.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelEvent"
            example:
              id: evt_01JRQ8F4X9N2K7YB3C5V6W8H0T
              event: channel
              channel_id: ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
              timestamp: 1789459321000
              api_version: v1
              data:
                status: connected
                previous_status: qr
                phone: "15557654321"
      responses:
        "200":
          description: >-
            Any 2xx marks the delivery successful. Answer within 10 s and do the work afterwards; Wapito
            retries up to 5 times with exponential backoff and opens a circuit breaker on a persistently
            failing endpoint.
        4XX:
          description: Wapito records the failure and retries the delivery according to the backoff schedule.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Channel token `wpt_…`. Create one per channel in the dashboard; it is shown once and stored only as a
        hash. Send it as `Authorization: Bearer wpt_…`. The token scopes every request to exactly one channel,
        so there is no channel id in any path.
  parameters:
    WebhookEventHeader:
      name: X-Wapito-Event
      in: header
      required: true
      description: Name of the delivered event, identical to `event` in the body.
      schema:
        type: string
        examples:
          - messages
    WebhookDeliveryHeader:
      name: X-Wapito-Delivery
      in: header
      required: true
      description: Id of the delivery job. Stable across retries, so it is the right idempotency key.
      schema:
        type: string
        examples:
          - whj_01JRQ8F4X9N2K7YB3C5V6W8H0T
    WebhookChannelHeader:
      name: X-Wapito-Channel
      in: header
      required: true
      description: Channel that produced the event, matching `channel_id` in the body.
      schema:
        type: string
        examples:
          - ch_01JRQ8F4X9N2K7YB3C5V6W8H0T
    WebhookSignatureHeader:
      name: X-Wapito-Signature
      in: header
      required: true
      description: >-
        HMAC of the raw body as `t=<unix ms>,v1=<hex HMAC-SHA256(secret, "<t>.<rawBody>")>`. Verify against
        the raw bytes before parsing, and reject the delivery when `|now - t| > 300 s`.
      schema:
        type: string
        examples:
          - t=1789459200123,v1=5f8a1c0b7d2e4936ab55c1e7f04d9b83c6a2ef10d4b79325c8f6a0d1e2b3c4d5
  schemas:
    Error:
      type: object
      title: Error
      description: >-
        Every non-2xx response from the Wapito API uses this envelope. The `code` is stable and documented in
        the error registry that also drives `/docs/errors/`; clients branch on `code`, never on the
        human-readable `message`.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              description: Stable machine-readable error code from the Wapito error registry.
              enum:
                - invalid_request
                - invalid_recipient
                - unauthorized
                - token_revoked
                - plan_required
                - forbidden
                - channel_locked
                - not_found
                - not_on_whatsapp
                - message_not_found
                - channel_not_connected
                - channel_not_in_qr_state
                - business_account_required
                - payload_too_large
                - unsupported_media_type
                - webhook_url_invalid
                - reachout_timelocked
                - rate_limited
                - quota_exceeded
                - send_rate_limited
                - cold_send_limit
                - warmup_limit
                - internal_error
                - engine_unsupported_feature
                - feature_not_available
                - engine_error
                - engine_unavailable
                - engine_timeout
            message:
              type: string
              description: Human-readable explanation. Wording may change; never parse it.
            details:
              type: object
              description: >-
                Code-specific context, for example `issues[]` for `invalid_request`, `checkout_url`
                (plus `feature`, `plan`, `limit` and `price_ids`) for `plan_required`, `until` for
                `reachout_timelocked`, `hint` for `engine_unsupported_feature`.
              additionalProperties: true
            request_id:
              type: string
              description: Correlation id for this request; quote it in support tickets.
    Recipient:
      type: string
      title: Recipient
      description: >-
        A WhatsApp recipient. Accepted forms: `+E.164` (`+15551234567`), bare digits (`15551234567`),
        `<digits>@s.whatsapp.net`, `<digits>@c.us`, `<group id>@g.us`, `<lid>@lid` and `<channel
        id>@newsletter`. Phone forms are validated with libphonenumber and rejected with `invalid_recipient`
        when they are not dialable. Wapito normalises the value before it reaches the engine and always
        returns `@s.whatsapp.net` ids.
      examples:
        - "+15551234567"
        - 15551234567@s.whatsapp.net
        - 120363041234567890@g.us
    MessageMedia:
      type: object
      title: MessageMedia
      description: Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
      required:
        - id
        - link
        - mime_type
        - file_size
        - file_name
        - caption
      properties:
        id:
          type:
            - string
            - "null"
          description: Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
        link:
          type:
            - string
            - "null"
          description: Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
        mime_type:
          type: string
          description: IANA media type reported by WhatsApp.
        file_size:
          type:
            - integer
            - "null"
          description: Size in bytes.
        file_name:
          type:
            - string
            - "null"
          description: Original file name when the sender supplied one.
        caption:
          type:
            - string
            - "null"
          description: Caption sent alongside the media.
        error:
          type: string
          description: Set to `unavailable` when the media could not be copied before it expired on the engine.
          enum:
            - unavailable
    MessageLocation:
      type: object
      title: MessageLocation
      description: Location payload of a `location` message.
      required:
        - latitude
        - longitude
        - name
        - address
      properties:
        latitude:
          type: number
          description: Decimal degrees.
        longitude:
          type: number
          description: Decimal degrees.
        name:
          type:
            - string
            - "null"
          description: Place name shown in the bubble.
        address:
          type:
            - string
            - "null"
          description: Street address shown under the place name.
    MessageContact:
      type: object
      title: MessageContact
      description: Contact card payload of a `contact` message.
      required:
        - vcard
      properties:
        vcard:
          type: string
          description: Raw vCard 3.0 text as WhatsApp stores it.
    MessagePoll:
      type: object
      title: MessagePoll
      description: Poll payload of a `poll` message. Votes arrive later as `polls` webhook events.
      required:
        - title
        - options
        - multiple
      properties:
        title:
          type: string
          description: Poll question.
        options:
          type: array
          description: Answer options in display order.
          items:
            type: string
        multiple:
          type: boolean
          description: Whether voters may select more than one option.
    MessageContext:
      type: object
      title: MessageContext
      description: Reply, forward and mention context for a message.
      required:
        - quoted_id
        - quoted_author
        - forwarded
        - mentions
      properties:
        quoted_id:
          type:
            - string
            - "null"
          description: Id of the quoted message, when this message is a reply.
        quoted_author:
          type:
            - string
            - "null"
          description: Sender of the quoted message.
        forwarded:
          type: boolean
          description: Whether WhatsApp marked the message as forwarded.
        mentions:
          type: array
          description: Phone numbers mentioned with @ in the body.
          items:
            type: string
    Message:
      type: object
      title: Message
      description: >-
        The Wapito message object. It is the same shape everywhere: the body of a send response, the item of
        `GET /messages/list/{chat_id}` and the `data` of a `messages` webhook event. Exactly one of the typed
        payload fields (`text`, `image`, `video`, `audio`, `voice`, `document`, `sticker`, `location`,
        `contact`, `poll`) is present, chosen by `type`.
      required:
        - id
        - chat_id
        - from_me
        - from
        - from_lid
        - from_name
        - participant
        - type
        - timestamp
        - source
      properties:
        id:
          type: string
          description: Engine message id, unique within the channel.
        chat_id:
          type: string
          description: Chat the message belongs to, as `@s.whatsapp.net`, `@g.us` or `@newsletter`.
        from_me:
          type: boolean
          description: True when the channel sent the message, false when it received it.
        from:
          type:
            - string
            - "null"
          description: >-
            Sender in digits for direct chats, or the group id for group chats. `null` when only a LID is
            known.
        from_lid:
          type:
            - string
            - "null"
          description: Sender LID when WhatsApp addressed the message by LID rather than phone number.
        from_name:
          type:
            - string
            - "null"
          description: Push name of the sender as WhatsApp reports it.
        participant:
          type:
            - string
            - "null"
          description: In group chats, the individual sender behind `from`.
        type:
          type: string
          description: Message kind; selects which payload field is populated.
          enum:
            - text
            - image
            - video
            - audio
            - voice
            - document
            - sticker
            - location
            - contact
            - poll
            - reaction
            - unknown
        timestamp:
          type: integer
          description: Unix epoch milliseconds.
        source:
          type: string
          description: "`app` when the message came from the linked phone, `api` when Wapito sent it."
          enum:
            - app
            - api
        text:
          type: object
          description: Body of a `text` message.
          required:
            - body
          properties:
            body:
              type: string
              description: Message text, WhatsApp markdown allowed.
        image:
          $ref: "#/components/schemas/MessageMedia"
        video:
          $ref: "#/components/schemas/MessageMedia"
        audio:
          $ref: "#/components/schemas/MessageMedia"
        voice:
          $ref: "#/components/schemas/MessageMedia"
        document:
          $ref: "#/components/schemas/MessageMedia"
        sticker:
          $ref: "#/components/schemas/MessageMedia"
        location:
          $ref: "#/components/schemas/MessageLocation"
        contact:
          $ref: "#/components/schemas/MessageContact"
        poll:
          $ref: "#/components/schemas/MessagePoll"
        context:
          $ref: "#/components/schemas/MessageContext"
        status:
          type: string
          description: Delivery state of an outbound message.
          enum:
            - failed
            - pending
            - sent
            - delivered
            - read
            - played
        raw:
          type: object
          description: Untouched engine payload. Present only while `settings.include_raw` is enabled.
          additionalProperties: true
    Chat:
      type: object
      title: Chat
      description: "A conversation the channel takes part in: a direct chat, a group or a WhatsApp Channel."
      required:
        - id
        - name
        - type
        - unread_count
        - archived
        - pinned
        - muted_until
        - timestamp
        - picture
        - last_message
      properties:
        id:
          type: string
          description: Chat id (`@s.whatsapp.net`, `@g.us` or `@newsletter`).
        name:
          type:
            - string
            - "null"
          description: "Display name: contact name, group subject or channel name."
        type:
          type: string
          description: Chat kind.
          enum:
            - individual
            - group
            - newsletter
        unread_count:
          type: integer
          description: Unread messages the linked phone still shows.
        archived:
          type: boolean
          description: Whether the chat is archived.
        pinned:
          type: boolean
          description: Whether the chat is pinned to the top of the list.
        muted_until:
          type:
            - integer
            - "null"
          description: Unix epoch milliseconds the mute expires, or `null`.
        timestamp:
          type:
            - integer
            - "null"
          description: Unix epoch milliseconds of the most recent message.
        picture:
          type:
            - string
            - "null"
          description: URL of the chat picture, when the engine could resolve it.
        last_message:
          description: Most recent message in the chat, when the engine stores history.
          oneOf:
            - $ref: "#/components/schemas/Message"
            - type: "null"
    Contact:
      type: object
      title: Contact
      description: A WhatsApp contact known to the channel, keyed by its `@s.whatsapp.net` id.
      required:
        - id
        - phone
        - lid
        - name
        - push_name
        - short_name
        - is_business
        - is_me
        - is_blocked
        - picture
      properties:
        id:
          type: string
          description: Contact id in `@s.whatsapp.net` form.
        phone:
          type:
            - string
            - "null"
          description: Phone number in digits, without `+`.
        lid:
          type:
            - string
            - "null"
          description: Linked device identifier (LID) when WhatsApp exposes one for this contact.
        name:
          type:
            - string
            - "null"
          description: Name from the linked phone address book.
        push_name:
          type:
            - string
            - "null"
          description: Name the contact publishes to others.
        short_name:
          type:
            - string
            - "null"
          description: Shortened display name.
        is_business:
          type: boolean
          description: Whether the account is a WhatsApp Business account.
        is_me:
          type: boolean
          description: Whether this contact is the channel itself.
        is_blocked:
          type: boolean
          description: Whether the channel has blocked this contact.
        picture:
          type:
            - string
            - "null"
          description: Profile picture URL.
    ContactCheckResult:
      type: object
      title: ContactCheckResult
      description: Outcome of checking one phone number against WhatsApp registration.
      required:
        - phone
        - exists
        - jid
        - lid
      properties:
        phone:
          type: string
          description: The number exactly as it was submitted.
        exists:
          type: boolean
          description: Whether the number is registered on WhatsApp.
        jid:
          type:
            - string
            - "null"
          description: Canonical `@s.whatsapp.net` id when the number exists.
        lid:
          type:
            - string
            - "null"
          description: LID for the number when the engine returned one.
    GroupSettings:
      type: object
      title: GroupSettings
      description: Group security settings; every field maps to one WhatsApp admin toggle.
      required:
        - info_admin_only
        - messages_admin_only
        - member_add_mode
        - membership_approval
      properties:
        info_admin_only:
          type: boolean
          description: Only admins may change subject, description and icon.
        messages_admin_only:
          type: boolean
          description: Only admins may post (announcement group).
        member_add_mode:
          type: string
          description: Who may add participants directly.
          enum:
            - admin_add
            - all_member_add
        membership_approval:
          type: boolean
          description: New joiners land in the join-request queue instead of the group.
    UpdateGroupSettingsRequest:
      type: object
      title: UpdateGroupSettingsRequest
      description: Body of `PATCH /groups/{id}/settings`. Only the settings you send are changed, in this order; the
        answer is the complete `GroupSettings` afterwards.
      properties:
        info_admin_only:
          type: boolean
          description: Only admins may change subject, description and icon.
        messages_admin_only:
          type: boolean
          description: Only admins may post (announcement group).
        member_add_mode:
          type: string
          description: Who may add participants directly.
          enum:
            - admin_add
            - all_member_add
        membership_approval:
          type: boolean
          description: New joiners land in the join-request queue instead of the group.
    Group:
      type: object
      title: Group
      description: >-
        A WhatsApp group. Creating groups and adding participants are the highest ban-risk operations on this
        API.
      required:
        - id
        - subject
        - description
        - owner
        - created_at
        - participants_count
        - is_announce
        - is_locked
        - is_community_announce
        - invite_code
        - picture
        - settings
      properties:
        id:
          type: string
          description: Group id ending in `@g.us`.
        subject:
          type: string
          description: Group name shown in the chat list.
        description:
          type:
            - string
            - "null"
          description: Group description shown in group info.
        owner:
          type:
            - string
            - "null"
          description: Id of the account that created the group.
        created_at:
          type:
            - string
            - "null"
          description: ISO 8601 creation timestamp.
          format: date-time
        participants_count:
          type: integer
          description: Number of participants including admins.
        participants:
          type: array
          description: Every participant, on single-group answers (create, get, update, invite accept and preview). List
            pages leave it out — read the group to get it.
          items:
            $ref: "#/components/schemas/GroupParticipant"
        is_announce:
          type: boolean
          description: True when only admins may post.
        is_locked:
          type: boolean
          description: True when only admins may edit group info.
        is_community_announce:
          type: boolean
          description: True when this is a community announcement group.
        invite_code:
          type:
            - string
            - "null"
          description: Current invite code, when the channel is an admin.
        picture:
          type:
            - string
            - "null"
          description: Group icon URL.
        settings:
          $ref: "#/components/schemas/GroupSettings"
    GroupParticipant:
      type: object
      title: GroupParticipant
      description: One member of a group, with the role WhatsApp currently assigns them.
      required:
        - id
        - phone
        - lid
        - name
        - role
        - joined_at
      properties:
        id:
          type: string
          description: Participant id in `@s.whatsapp.net` form.
        phone:
          type:
            - string
            - "null"
          description: Phone number in digits.
        lid:
          type:
            - string
            - "null"
          description: LID when the participant is addressed by LID.
        name:
          type:
            - string
            - "null"
          description: Display name known to the channel.
        role:
          type: string
          description: Role inside the group.
          enum:
            - member
            - admin
            - superadmin
        joined_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp the participant joined, when known.
          format: date-time
    GroupInvite:
      type: object
      title: GroupInvite
      description: >-
        The invite code of a group and the shareable link built from it. Treat both as credentials: anyone
        holding the link can join.
      required:
        - code
        - link
      properties:
        code:
          type: string
          description: Invite code, the tail of the link.
        link:
          type: string
          description: Full `https://chat.whatsapp.com/<code>` link.
          format: uri
    GroupParticipantChangeResult:
      type: object
      title: GroupParticipantChangeResult
      description: >-
        Per-participant outcome of a bulk membership or role change. WhatsApp can refuse individual people
        while accepting the rest of the batch, so always read this list instead of assuming the whole call
        succeeded.
      required:
        - results
      properties:
        results:
          type: array
          description: One entry per submitted participant, in submission order.
          items:
            type: object
            required:
              - id
              - status
            properties:
              id:
                type: string
                description: Participant id in `@s.whatsapp.net` form.
              status:
                type: string
                description: What WhatsApp did with this participant.
                enum:
                  - added
                  - removed
                  - promoted
                  - demoted
                  - invite_required
                  - failed
              reason:
                type: string
                description: Why WhatsApp refused, when it did.
    Community:
      type: object
      title: Community
      description: A WhatsApp Community grouping several groups behind one announcement group. Phase 2.
      required:
        - id
        - name
        - description
        - owner
        - groups_count
        - announcement_group_id
        - created_at
      properties:
        id:
          type: string
          description: Community id ending in `@g.us`.
        name:
          type: string
          description: Community name.
        description:
          type:
            - string
            - "null"
          description: Community description.
        owner:
          type:
            - string
            - "null"
          description: Id of the account that created the community.
        groups_count:
          type: integer
          description: Number of linked groups.
        announcement_group_id:
          type:
            - string
            - "null"
          description: Id of the announcement group.
        created_at:
          type:
            - string
            - "null"
          description: ISO 8601 creation timestamp.
          format: date-time
    Newsletter:
      type: object
      title: Newsletter
      description: >-
        A WhatsApp Channel (called a newsletter at protocol level) the account owns, follows or was invited to.
      required:
        - id
        - name
        - description
        - invite_code
        - invite_link
        - picture
        - subscribers_count
        - verified
        - role
        - created_at
      properties:
        id:
          type: string
          description: Channel id ending in `@newsletter`.
        name:
          type: string
          description: Channel name.
        description:
          type:
            - string
            - "null"
          description: Channel description.
        invite_code:
          type:
            - string
            - "null"
          description: Public invite code, the tail of a `whatsapp.com/channel/…` link.
        invite_link:
          type:
            - string
            - "null"
          format: uri
          description: The full `https://whatsapp.com/channel/<invite_code>` link, ready to share.
        picture:
          type:
            - string
            - "null"
          description: Channel picture URL.
        subscribers_count:
          type:
            - integer
            - "null"
          description: Follower count when WhatsApp exposes it.
        verified:
          type: boolean
          description: Whether WhatsApp verified the channel.
        role:
          type: string
          description: >-
            Relationship of this channel to the account. Directory search results carry no relationship and
            are reported as `guest`.
          enum:
            - owner
            - admin
            - subscriber
            - guest
        created_at:
          type:
            - string
            - "null"
          description: ISO 8601 creation timestamp. The engines do not expose it yet, so it is `null` today.
          format: date-time
    Story:
      type: object
      title: Story
      description: A status update (story) posted from the channel to selected contacts.
      required:
        - id
        - type
        - timestamp
        - link
        - caption
        - contacts
      properties:
        id:
          type: string
          description: Status message id.
        type:
          type: string
          description: Kind of status posted.
          enum:
            - text
            - image
            - video
            - audio
        timestamp:
          type: integer
          description: Unix epoch milliseconds the status was posted.
        link:
          type:
            - string
            - "null"
          format: uri
          description: >-
            Signed Wapito media link when the status was posted from a media id returned by `POST /media`,
            the one case where Wapito holds the file. `null` for a text status and for a file that came as
            a URL or a data URI.
        caption:
          type:
            - string
            - "null"
          description: Caption or body text.
        contacts:
          type: array
          description: The audience as submitted, normalised to digits. Empty means every contact.
          items:
            type: string
    Label:
      type: object
      title: Label
      description: >-
        A WhatsApp Business label. Labels exist only on Business accounts; other accounts get
        `business_account_required`.
      required:
        - id
        - name
        - color
        - color_hex
        - predefined_id
      properties:
        id:
          type: string
          description: Label id assigned by WhatsApp.
        name:
          type: string
          description: Label text.
        color:
          type:
            - integer
            - "null"
          description: WhatsApp colour index 0-19; `null` when the engine did not report one.
        color_hex:
          type:
            - string
            - "null"
          description: Hex rendering of `color` for UI use.
        predefined_id:
          type:
            - string
            - "null"
          description: >-
            Set when the label is one of the WhatsApp presets (New customer, Paid, …). The engines do not
            surface preset ids yet, so it is `null` today.
    BusinessProfile:
      type: object
      title: BusinessProfile
      description: Public business profile of a WhatsApp Business account. Phase 2.
      required:
        - id
        - name
        - category
        - description
        - email
        - website
        - address
        - latitude
        - longitude
        - business_hours
      properties:
        id:
          type: string
          description: Account id in `@s.whatsapp.net` form.
        name:
          type:
            - string
            - "null"
          description: Business display name.
        category:
          type:
            - string
            - "null"
          description: Business category chosen in WhatsApp Business.
        description:
          type:
            - string
            - "null"
          description: Business description.
        email:
          type:
            - string
            - "null"
          description: Contact email.
        website:
          type: array
          description: Websites listed on the profile.
          items:
            type: string
        address:
          type:
            - string
            - "null"
          description: Street address.
        latitude:
          type:
            - number
            - "null"
          description: Decimal degrees.
        longitude:
          type:
            - number
            - "null"
          description: Decimal degrees.
        business_hours:
          type:
            - string
            - "null"
          description: Opening hours as WhatsApp renders them.
    CatalogProduct:
      type: object
      title: CatalogProduct
      description: One product in a WhatsApp Business catalog. Phase 2.
      required:
        - id
        - retailer_id
        - name
        - description
        - price
        - currency
        - availability
        - url
        - images
      properties:
        id:
          type: string
          description: WhatsApp product id.
        retailer_id:
          type:
            - string
            - "null"
          description: Your own SKU as uploaded to the catalog.
        name:
          type: string
          description: Product name.
        description:
          type:
            - string
            - "null"
          description: Product description.
        price:
          type:
            - integer
            - "null"
          description: Price in the smallest currency unit.
        currency:
          type:
            - string
            - "null"
          description: ISO 4217 currency code.
        availability:
          type:
            - string
            - "null"
          description: Stock state as WhatsApp reports it.
        url:
          type:
            - string
            - "null"
          description: Product page URL.
        images:
          type: array
          description: Image URLs.
          items:
            type: string
    BusinessOrder:
      type: object
      title: BusinessOrder
      description: A cart submitted by a customer from a WhatsApp Business catalog. Phase 2.
      required:
        - id
        - chat_id
        - token
        - status
        - currency
        - subtotal
        - total
        - items
        - created_at
      properties:
        id:
          type: string
          description: Order id.
        chat_id:
          type: string
          description: Chat the order arrived in.
        token:
          type:
            - string
            - "null"
          description: Order token WhatsApp uses to fetch details.
        status:
          type:
            - string
            - "null"
          description: Order state.
        currency:
          type:
            - string
            - "null"
          description: ISO 4217 currency code.
        subtotal:
          type:
            - integer
            - "null"
          description: Subtotal in the smallest currency unit.
        total:
          type:
            - integer
            - "null"
          description: Total in the smallest currency unit.
        items:
          type: array
          description: Ordered line items.
          items:
            type: object
            required:
              - product_id
              - name
              - quantity
              - price
            properties:
              product_id:
                type: string
                description: WhatsApp product id.
              name:
                type: string
                description: Product name at order time.
              quantity:
                type: integer
                description: Units ordered.
              price:
                type: integer
                description: Unit price in the smallest currency unit.
        created_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp.
          format: date-time
    Webhook:
      type: object
      title: Webhook
      description: >-
        A webhook subscription. The signing secret is never returned in full after creation, only as a preview.
      required:
        - id
        - url
        - events
        - enabled
        - headers
        - secret_preview
        - created_at
      properties:
        id:
          type: string
          description: Webhook id (`whk_` + ULID).
        url:
          type: string
          description: HTTPS endpoint Wapito posts events to.
          format: uri
        events:
          type: array
          description: "Subscribed events: exact names, prefix wildcards such as `messages.*`, or `*`."
          items:
            type: string
        enabled:
          type: boolean
          description: Disabled webhooks are skipped without queueing jobs.
        headers:
          type: object
          description: Extra headers sent with every delivery.
          additionalProperties:
            type: string
        secret_preview:
          type:
            - string
            - "null"
          description: Masked signing secret, e.g. `whsec_…f31a`.
        created_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp.
          format: date-time
    CreatedWebhook:
      title: CreatedWebhook
      description: "The answer of `POST /webhooks`: the webhook plus its signing secret, returned here and never again."
      allOf:
        - $ref: "#/components/schemas/Webhook"
        - type: object
          required:
            - secret
          properties:
            secret:
              type: string
              description: The `whsec_…` signing secret for `X-Wapito-Signature`. Store it now.
    ProxyConfig:
      type: object
      title: ProxyConfig
      description: Outbound proxy the engine should use for this channel. Premium plans only.
      required:
        - url
      properties:
        url:
          type: string
          description: Proxy URL, for example `http://gw.proxy.example:8000`.
        username:
          type:
            - string
            - "null"
          description: Proxy username.
        password:
          type:
            - string
            - "null"
          description: "Proxy password. Write-only: reads return `null`."
    ChannelSettings:
      type: object
      title: ChannelSettings
      description: Per-channel behaviour flags, including the anti-ban knobs described in the sending guide.
      required:
        - send_delay_ms
        - typing_simulation
        - include_raw
        - auto_read
        - reject_calls
        - proxy
      properties:
        send_delay_ms:
          type: integer
          description: Spacing between queued sends in milliseconds; minimum 500 on sandbox.
        typing_simulation:
          type: string
          description: "`auto` types for roughly 60 ms per character before sending, capped at 8 s."
          enum:
            - "off"
            - auto
        include_raw:
          type: boolean
          description: Attach the untouched engine payload as `raw` on webhook events.
        auto_read:
          type: boolean
          description: Mark inbound messages as read automatically.
        reject_calls:
          type: boolean
          description: Reject incoming WhatsApp calls automatically.
        proxy:
          description: Outbound proxy, or `null` for a direct connection.
          oneOf:
            - $ref: "#/components/schemas/ProxyConfig"
            - type: "null"
    ChannelInfo:
      type: object
      title: ChannelInfo
      description: >-
        Current state of the channel: which number is linked, which engine runs it, which plan applies.
      required:
        - id
        - name
        - engine
        - plan
        - status
        - phone
        - lid
        - push_name
        - connected_at
        - last_seen_at
        - created_at
        - webhooks_count
        - settings
      properties:
        id:
          type: string
          description: Channel id (`ch_` + ULID).
        name:
          type: string
          description: Name you gave the channel in the dashboard.
        engine:
          type: string
          description: Protocol engine running this channel.
          enum:
            - gows
            - noweb
        plan:
          type: string
          description: Billing plan in force.
          enum:
            - sandbox
            - premium
        status:
          type: string
          description: Connection state mirrored from the engine session.
          enum:
            - created
            - qr
            - pairing
            - connected
            - disconnected
            - logged_out
            - banned
        phone:
          type:
            - string
            - "null"
          description: Linked phone number in digits, once connected.
        lid:
          type:
            - string
            - "null"
          description: LID of the linked account.
        push_name:
          type:
            - string
            - "null"
          description: Profile name the linked account publishes.
        connected_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp of the current link.
          format: date-time
        last_seen_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp of the last engine heartbeat.
          format: date-time
        created_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp the channel was created.
          format: date-time
        webhooks_count:
          type: integer
          description: Number of configured webhooks.
        settings:
          $ref: "#/components/schemas/ChannelSettings"
    ChannelHealth:
      type: object
      title: ChannelHealth
      description: "Liveness view of the channel: our status next to the engine session state behind it, after a
        live ping of the engine. Suitable for an uptime probe."
      required:
        - status
        - engine
        - session
      properties:
        status:
          type: string
          description: Connection state, same vocabulary as `ChannelInfo.status`.
          enum:
            - created
            - qr
            - pairing
            - connected
            - disconnected
            - logged_out
            - banned
        engine:
          type: string
          description: Protocol engine running this channel.
          enum:
            - gows
            - noweb
        session:
          type:
            - string
            - "null"
          description: The engine’s own session status (`WORKING`, `SCAN_QR_CODE`, `STOPPED`, …), or `null` when the
            engine has no session for this channel.
    QuotaWindow:
      type: object
      title: QuotaWindow
      description: >-
        One metered counter and the cap that applies to it. A `null` cap means unlimited on this plan.
      required:
        - used
        - cap
        - resets_at
      properties:
        used:
          type: integer
          description: Units consumed in the current window.
        cap:
          type:
            - integer
            - "null"
          description: Cap for the window, or `null` when unlimited.
        resets_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp the window rolls over.
          format: date-time
    ChannelLimits:
      type: object
      title: ChannelLimits
      description: >-
        Everything that can throttle the channel right now: plan quotas, the warm-up ladder, the cold-send
        guard, the WhatsApp timelock and the engine capping state. Read it before a bulk run instead of
        discovering the cap through 429s.
      required:
        - plan
        - rate_limit_per_minute
        - media_max_bytes
        - webhooks_max
        - sent
        - number_checks
        - api_requests
        - warmup
        - cold_send
      properties:
        plan:
          type: string
          description: Billing plan in force.
          enum:
            - sandbox
            - premium
        rate_limit_per_minute:
          type: integer
          description: Requests per minute allowed across the whole API.
        media_max_bytes:
          type: integer
          description: Largest upload accepted by `POST /media` and the media send endpoints.
        webhooks_max:
          type: integer
          description: Maximum number of webhook subscriptions.
        sent:
          $ref: "#/components/schemas/QuotaWindow"
        number_checks:
          $ref: "#/components/schemas/QuotaWindow"
        api_requests:
          $ref: "#/components/schemas/QuotaWindow"
        warmup:
          type: object
          description: Warm-up ladder state for a freshly linked number.
          required:
            - day
            - cap
            - ladder_complete
          properties:
            day:
              type: integer
              description: Days since the number connected, starting at 0.
            cap:
              type:
                - integer
                - "null"
              description: Sends allowed today by the ladder, or `null` once the ladder is complete.
            ladder_complete:
              type: boolean
              description: True once the plan cap replaces the ladder.
        cold_send:
          type: object
          description: Guard on first messages to recipients who never wrote to you.
          required:
            - window_cap
            - used
            - resets_at
          properties:
            window_cap:
              type: integer
              description: Cold sends allowed in the current hour.
            used:
              type: integer
              description: Cold sends already made this hour.
            resets_at:
              type:
                - string
                - "null"
              description: ISO 8601 timestamp the hourly window resets.
              format: date-time
        timelock:
          type: object
          description: WhatsApp reachout timelock reported by the engine.
          required:
            - active
            - until
            - reason
          properties:
            active:
              type: boolean
              description: True while outbound first contact is blocked.
            until:
              type:
                - string
                - "null"
              description: ISO 8601 timestamp the lock lifts.
              format: date-time
            reason:
              type:
                - string
                - "null"
              description: Reason string the engine supplied.
        capping:
          type: object
          description: Engine-side capping counters (WhatsApp new-recipient cap).
          required:
            - unlimited
            - usedQuota
            - totalQuota
            - remaining
            - reset_at
          properties:
            unlimited:
              type: boolean
              description: True when WhatsApp reports no cap for this account.
            usedQuota:
              type: integer
              description: New recipients already messaged in the current window, under the engine’s own name.
            totalQuota:
              type:
                - integer
                - "null"
              description: Size of the window, or `null` when `unlimited`.
            remaining:
              type:
                - integer
                - "null"
              description: New recipients still allowed in the current window.
            reset_at:
              type:
                - string
                - "null"
              description: ISO 8601 timestamp the cap resets.
              format: date-time
    Usage:
      type: object
      title: Usage
      description: Metered usage for the channel over a date range, both per day and as a total.
      required:
        - from
        - to
        - totals
        - days
        - limits
        - month
      properties:
        from:
          type: string
          description: First day included, `YYYY-MM-DD`.
          format: date
        to:
          type: string
          description: Last day included, `YYYY-MM-DD`.
          format: date
        totals:
          $ref: "#/components/schemas/UsageCounters"
        days:
          type: array
          description: Per-day breakdown, oldest first.
          items:
            allOf:
              - type: object
                required:
                  - date
                properties:
                  date:
                    type: string
                    description: Day, `YYYY-MM-DD`.
                    format: date
              - $ref: "#/components/schemas/UsageCounters"
        limits:
          type: object
          description: The plan caps the counters are measured against; `null` means uncapped.
          required:
            - sent_per_day
            - number_checks_per_day
            - api_requests_per_month
          properties:
            sent_per_day:
              type:
                - integer
                - "null"
              description: Messages per UTC day.
            number_checks_per_day:
              type:
                - integer
                - "null"
              description: Number checks per UTC day.
            api_requests_per_month:
              type:
                - integer
                - "null"
              description: API requests per calendar month.
        month:
          type: object
          description: The monthly `api_requests` allowance — a month, not a day, so it stands apart from `days`.
          required:
            - api_requests
            - cap
          properties:
            api_requests:
              type: integer
              description: Requests made so far this calendar month.
            cap:
              type:
                - integer
                - "null"
              description: Requests allowed this month, or `null` when uncapped.
    UsageCounters:
      type: object
      title: UsageCounters
      description: The five counters Wapito meters per channel.
      required:
        - sent
        - received
        - api_requests
        - number_checks
        - webhook_deliveries
      properties:
        sent:
          type: integer
          description: Messages accepted for sending (`sent` quota).
        received:
          type: integer
          description: Inbound messages processed.
        api_requests:
          type: integer
          description: Billable API requests (`req` quota).
        number_checks:
          type: integer
          description: Numbers checked against WhatsApp (`chk` quota).
        webhook_deliveries:
          type: integer
          description: Webhook delivery attempts that reached your endpoint.
    MediaFile:
      type: object
      title: MediaFile
      description: >-
        A file stored by Wapito, either uploaded by you or copied out of an inbound message before the engine
        expires it.
      required:
        - id
        - channel_id
        - message_id
        - mimetype
        - size_bytes
        - filename
        - link
        - created_at
        - expires_at
      properties:
        id:
          type: string
          description: Media id (`med_` + ULID).
        channel_id:
          type: string
          description: Channel that owns the file.
        message_id:
          type:
            - string
            - "null"
          description: Message the file was extracted from, when it came from an inbound message.
        mimetype:
          type: string
          description: IANA media type.
        size_bytes:
          type: integer
          description: File size in bytes.
        filename:
          type:
            - string
            - "null"
          description: Original file name.
        link:
          type: string
          description: Signed `/v1/media/{id}?exp&sig` URL.
          format: uri
        created_at:
          type: string
          description: ISO 8601 timestamp.
          format: date-time
        expires_at:
          type: string
          description: ISO 8601 timestamp after which the file and its signature are rejected.
          format: date-time
    PresenceInfo:
      type: object
      title: PresenceInfo
      description: Last known presence for a chat. You only receive updates for chats you subscribed to.
      required:
        - chat_id
        - presence
        - last_seen
        - participants
      properties:
        chat_id:
          type: string
          description: Chat the presence belongs to.
        presence:
          type: string
          description: Current presence state.
          enum:
            - online
            - offline
            - typing
            - recording
            - paused
        last_seen:
          type:
            - integer
            - "null"
          description: Unix epoch milliseconds of the last time the contact was online, when they share it.
        participants:
          type: array
          description: Per-participant presence in group chats.
          items:
            type: object
            required:
              - id
              - presence
              - last_seen
            properties:
              id:
                type: string
                description: Participant id.
              presence:
                type: string
                description: Presence state for this participant.
                enum:
                  - online
                  - offline
                  - typing
                  - recording
                  - paused
              last_seen:
                type:
                  - integer
                  - "null"
                description: Unix epoch milliseconds.
    QrCode:
      type: object
      title: QrCode
      description: A short-lived pairing QR code for linking a phone to the channel.
      required:
        - data
        - format
        - expires_in
      properties:
        data:
          type: string
          description: Base64-encoded PNG bytes, without a data URL prefix.
        format:
          type: string
          description: Media type of `data`.
          enum:
            - image/png
        expires_in:
          type: integer
          description: Seconds before WhatsApp rotates the code.
    PairingCode:
      type: object
      title: PairingCode
      description: An eight-character code the user types into WhatsApp on their phone to link the channel.
      required:
        - code
        - expires_in
      properties:
        code:
          type: string
          description: Pairing code, shown as `XXXX-XXXX`.
        expires_in:
          type: integer
          description: Seconds before the code stops working.
    PictureResult:
      type: object
      title: PictureResult
      description: >-
        A profile, group or channel picture. `url` is `null` when WhatsApp privacy settings hide the image
        from this account.
      required:
        - url
      properties:
        url:
          type:
            - string
            - "null"
          description: WhatsApp CDN URL of the picture. The link expires, so download it if you need to keep it.
    LidResult:
      type: object
      title: LidResult
      description: The mapping between a phone number and the linked device identifier WhatsApp uses for it.
      required:
        - lid
        - phone
      properties:
        lid:
          type:
            - string
            - "null"
          description: Linked device identifier, including the `@lid` suffix.
        phone:
          type:
            - string
            - "null"
          description: Phone number in digits, when the direction of the lookup resolves one.
    AboutResult:
      type: object
      title: AboutResult
      description: The about line a contact publishes on their WhatsApp profile.
      required:
        - about
        - set_at
      properties:
        about:
          type:
            - string
            - "null"
          description: About text, or `null` when privacy settings hide it.
        set_at:
          type:
            - string
            - "null"
          description: ISO 8601 timestamp the text was last changed, when WhatsApp reports it.
          format: date-time
    Acknowledgement:
      type: object
      title: Acknowledgement
      description: >-
        Minimal body for operations whose only useful answer is "done", with the engine id when there is one.
      required:
        - ok
        - id
      properties:
        ok:
          type: boolean
          description: Always true on a 2xx response.
          const: true
        id:
          type:
            - string
            - "null"
          description: Id of the object the operation acted on, when applicable.
    MediaInput:
      type: object
      title: MediaInput
      description: Explicit media descriptor. Supply exactly one of `url` or `data`.
      properties:
        url:
          type: string
          description: Publicly reachable HTTPS URL Wapito downloads before sending.
          format: uri
        data:
          type: string
          description: Base64-encoded file bytes, without a data URL prefix.
        mimetype:
          type: string
          description: IANA media type; required with `data`, inferred from the response headers with `url`.
        filename:
          type: string
          description: File name shown to the recipient.
    MediaRef:
      title: MediaRef
      description: >-
        Where the file comes from. Pass a string to use an HTTPS URL, a `data:` URI or a Wapito media id
        returned by `POST /media`, or pass a `MediaInput` object when you need to override the media type or
        file name.
      oneOf:
        - type: string
          description: HTTPS URL, `data:` URI, or a `med_` media id.
        - $ref: "#/components/schemas/MediaInput"
    SendTextRequest:
      type: object
      title: SendTextRequest
      description: Body of `POST /messages/text`.
      required:
        - to
        - body
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        body:
          type: string
          description: >-
            Message text. WhatsApp markdown (`*bold*`, `_italic_`, ```` ``` ````) is passed through unchanged.
        quoted:
          type: string
          description: Id of a message in the same chat to reply to.
        mentions:
          type: array
          description: Phone numbers in digits to mention; each must also appear as `@number` in `body`.
          items:
            type: string
        no_link_preview:
          type: boolean
          description: Suppress the automatic link preview WhatsApp generates for the first URL.
        typing_time:
          type: integer
          description: Seconds of simulated typing before the message is sent, 0-15.
          minimum: 0
          maximum: 15
    SendMediaRequest:
      type: object
      title: SendMediaRequest
      description: >-
        Body shared by the image, video, document, sticker, audio, voice and short-video send endpoints.
      required:
        - to
        - media
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        media:
          $ref: "#/components/schemas/MediaRef"
        caption:
          type: string
          description: Caption rendered under the media. Ignored for stickers, audio and voice notes.
        filename:
          type: string
          description: Override the file name the recipient sees.
        quoted:
          type: string
          description: Id of a message in the same chat to reply to.
        mentions:
          type: array
          description: Phone numbers in digits to mention in the caption.
          items:
            type: string
        typing_time:
          type: integer
          description: Seconds of simulated typing before the message is sent, 0-15.
          minimum: 0
          maximum: 15
    SendLinkRequest:
      type: object
      title: SendLinkRequest
      description: >-
        Body of `POST /messages/link`: a text message whose link preview you control instead of letting
        WhatsApp scrape it.
      required:
        - to
        - url
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        url:
          type: string
          description: Link the preview points at.
          format: uri
        title:
          type: string
          description: Preview headline.
        description:
          type: string
          description: Preview description under the headline.
        image:
          $ref: "#/components/schemas/MediaRef"
        body:
          type: string
          description: Text shown above the preview card. Defaults to `url`.
        quoted:
          type: string
          description: Id of a message in the same chat to reply to.
    SendLocationRequest:
      type: object
      title: SendLocationRequest
      description: Body of `POST /messages/location`.
      required:
        - to
        - latitude
        - longitude
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        latitude:
          type: number
          description: Decimal degrees, -90 to 90.
          minimum: -90
          maximum: 90
        longitude:
          type: number
          description: Decimal degrees, -180 to 180.
          minimum: -180
          maximum: 180
        name:
          type: string
          description: Place name shown in the bubble.
        address:
          type: string
          description: Street address shown under the place name.
        quoted:
          type: string
          description: Id of a message in the same chat to reply to.
    SendContactRequest:
      type: object
      title: SendContactRequest
      description: >-
        Body of `POST /messages/contact`. Supply either a ready vCard or a contact id Wapito turns into one.
      required:
        - to
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        vcard:
          type: string
          description: Raw vCard 3.0 text.
        contact_id:
          type: string
          description: Contact to send as a card, in any accepted recipient form.
        name:
          type: string
          description: Display name for the card when building it from `contact_id`.
    SendPollRequest:
      type: object
      title: SendPollRequest
      description: >-
        Body of `POST /messages/poll`. Votes arrive later as `polls` webhook events, never in this response.
      required:
        - to
        - title
        - options
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        title:
          type: string
          description: Poll question, up to 255 characters.
          maxLength: 255
        options:
          type: array
          description: Between 2 and 12 answer options.
          items:
            type: string
          minItems: 2
          maxItems: 12
        multiple:
          type: boolean
          description: Allow voters to select more than one option. Defaults to false.
        quoted:
          type: string
          description: Id of a message in the same chat to reply to.
    ReactRequest:
      type: object
      title: ReactRequest
      description: Body of `POST /messages/{id}/react`.
      required:
        - emoji
      properties:
        emoji:
          type: string
          description: A single emoji. Sending an empty string also removes the reaction.
    ForwardRequest:
      type: object
      title: ForwardRequest
      description: Body of `POST /messages/{id}/forward`.
      required:
        - to
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        typing_time:
          type: integer
          description: Seconds of simulated typing before the forward is sent, 0-15.
          minimum: 0
          maximum: 15
    EditMessageRequest:
      type: object
      title: EditMessageRequest
      description: Body of `PATCH /messages/{id}`.
      required:
        - body
      properties:
        body:
          type: string
          description: Replacement text for the message.
    StarRequest:
      type: object
      title: StarRequest
      description: Body of `PUT /messages/{id}/star`.
      required:
        - star
      properties:
        star:
          type: boolean
          description: True to star the message, false to unstar it.
    InteractiveMessageRequest:
      type: object
      title: InteractiveMessageRequest
      description: Body of `POST /messages/interactive`. Phase 2.
      required:
        - to
        - body
        - buttons
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        header:
          type: string
          description: Optional header text above the body.
        body:
          type: string
          description: Main text of the interactive message.
        footer:
          type: string
          description: Small print under the buttons.
        buttons:
          type: array
          description: Between 1 and 3 buttons.
          items:
            type: object
            required:
              - id
              - title
              - type
            properties:
              id:
                type: string
                description: Value echoed back when the recipient taps the button.
              title:
                type: string
                description: Button label, up to 20 characters.
              type:
                type: string
                description: Button behaviour.
                enum:
                  - reply
                  - url
                  - call
                  - copy
    CarouselMessageRequest:
      type: object
      title: CarouselMessageRequest
      description: Body of `POST /messages/carousel`. Phase 2.
      required:
        - to
        - cards
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        body:
          type: string
          description: Text shown above the carousel.
        cards:
          type: array
          description: Between 2 and 10 cards.
          items:
            type: object
            required:
              - media
            properties:
              media:
                $ref: "#/components/schemas/MediaRef"
              title:
                type: string
                description: Card headline.
              description:
                type: string
                description: Card body text.
    QuizMessageRequest:
      type: object
      title: QuizMessageRequest
      description: Body of `POST /messages/quiz`. Phase 2.
      required:
        - to
        - title
        - options
        - correct_option
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        title:
          type: string
          description: Quiz question.
        options:
          type: array
          description: Answer options.
          items:
            type: string
        correct_option:
          type: integer
          description: Zero-based index of the correct option.
        explanation:
          type: string
          description: Text shown after the recipient answers.
    EventMessageRequest:
      type: object
      title: EventMessageRequest
      description: Body of `POST /messages/event`. Phase 2.
      required:
        - to
        - name
        - start_at
      properties:
        to:
          $ref: "#/components/schemas/Recipient"
        name:
          type: string
          description: Event name.
        description:
          type: string
          description: Event description.
        start_at:
          type: string
          description: ISO 8601 start time.
          format: date-time
        end_at:
          type: string
          description: ISO 8601 end time.
          format: date-time
        location:
          type: string
          description: Free-text location.
        call_link:
          type: string
          description: WhatsApp call link to attach to the event.
    UpdateChatRequest:
      type: object
      title: UpdateChatRequest
      description: Body of `PATCH /chats/{id}`.
      required:
        - unread
      properties:
        unread:
          type: boolean
          description: "Always `true`: marks the chat unread. Clearing the badge means reading the chat, which sends
            read receipts — use `PUT /messages/{id}/read` for that."
          const: true
    ArchiveChatRequest:
      type: object
      title: ArchiveChatRequest
      description: Body of `PATCH /chats/{id}/archive`.
      required:
        - archive
      properties:
        archive:
          type: boolean
          description: True archives the chat, false restores it to the main list.
    CheckContactsRequest:
      type: object
      title: CheckContactsRequest
      description: Body of `POST /contacts/check`.
      required:
        - phones
      properties:
        phones:
          type: array
          description: Up to 50 phone numbers per request, in `+E.164` or bare digits.
          items:
            type: string
          maxItems: 50
    UpdateContactRequest:
      type: object
      title: UpdateContactRequest
      description: Body of `PATCH /contacts/{id}`; writes into the address book of the linked phone.
      properties:
        first_name:
          type: string
          description: Given name.
        last_name:
          type: string
          description: Family name.
        name:
          type: string
          description: Full display name; used when the engine does not split names.
    MyPresenceRequest:
      type: object
      title: MyPresenceRequest
      description: Body of `POST /presence/me`.
      required:
        - presence
      properties:
        presence:
          type: string
          description: Global presence to publish for the channel.
          enum:
            - online
            - offline
    ChatPresenceRequest:
      type: object
      title: ChatPresenceRequest
      description: Body of `POST /presence/{chat_id}`.
      required:
        - presence
      properties:
        presence:
          type: string
          description: Typing indicator to publish in this chat.
          enum:
            - typing
            - recording
            - paused
    ChannelSettingsRequest:
      type: object
      title: ChannelSettingsRequest
      description: Body of `PATCH /channel/settings`. Only the fields you send are changed.
      properties:
        name:
          type: string
          description: Channel name shown in the dashboard.
        send_delay_ms:
          type: integer
          description: Spacing between queued sends, minimum 500 on sandbox.
          minimum: 500
        typing_simulation:
          type: string
          description: "`auto` types before each send, `off` sends immediately."
          enum:
            - "off"
            - auto
        include_raw:
          type: boolean
          description: Attach the untouched engine payload as `raw` on webhook events.
        auto_read:
          type: boolean
          description: Mark inbound messages as read automatically.
        reject_calls:
          type: boolean
          description: Reject incoming WhatsApp calls automatically.
        proxy:
          description: Outbound proxy for the engine, or `null` to clear it. Premium only.
          oneOf:
            - $ref: "#/components/schemas/ProxyConfig"
            - type: "null"
    PairingCodeRequest:
      type: object
      title: PairingCodeRequest
      description: Body of `POST /channel/pairing-code`.
      required:
        - phone
      properties:
        phone:
          type: string
          description: The number being linked, in `+E.164` or bare digits.
    UpdateProfileRequest:
      type: object
      title: UpdateProfileRequest
      description: "Body of `PATCH /users/profile`. Send `picture: null` to remove the current photo."
      properties:
        name:
          type: string
          description: Profile name other WhatsApp users see.
        status:
          type: string
          description: About text, up to 139 characters.
          maxLength: 139
        picture:
          description: New profile photo, or `null` to delete it.
          oneOf:
            - $ref: "#/components/schemas/MediaRef"
            - type: "null"
    RejectCallRequest:
      type: object
      title: RejectCallRequest
      description: Body of `POST /calls/reject`.
      required:
        - call_id
        - from
      properties:
        call_id:
          type: string
          description: Call id taken from the `calls` webhook event.
        from:
          type: string
          description: Caller, in any accepted recipient form.
    CreateGroupRequest:
      type: object
      title: CreateGroupRequest
      description: Body of `POST /groups`.
      required:
        - subject
        - participants
      properties:
        subject:
          type: string
          description: Group name, up to 100 characters.
          maxLength: 100
        participants:
          type: array
          description: Members to add at creation time, 1-1024 entries.
          items:
            $ref: "#/components/schemas/Recipient"
          minItems: 1
        description:
          type: string
          description: Group description set immediately after creation.
    UpdateGroupRequest:
      type: object
      title: UpdateGroupRequest
      description: Body of `PATCH /groups/{id}`. Only the fields you send are changed.
      properties:
        subject:
          type: string
          description: New group name, up to 100 characters.
          maxLength: 100
        description:
          type: string
          description: New group description.
    GroupParticipantsRequest:
      type: object
      title: GroupParticipantsRequest
      description: Body of the group participant and admin collection endpoints.
      required:
        - participants
      properties:
        participants:
          type: array
          description: Participants to act on, 1-50 per request.
          items:
            $ref: "#/components/schemas/Recipient"
          minItems: 1
          maxItems: 50
    GroupIconRequest:
      type: object
      title: GroupIconRequest
      description: Body of `PUT /groups/{id}/icon`. WhatsApp crops the image to a square.
      required:
        - media
      properties:
        media:
          $ref: "#/components/schemas/MediaRef"
    AcceptInviteRequest:
      type: object
      title: AcceptInviteRequest
      description: Body of `POST /groups/invite/accept`.
      required:
        - invite_code
      properties:
        invite_code:
          type: string
          description: Invite code, or the full `chat.whatsapp.com/…` link Wapito parses for you.
    CreateCommunityRequest:
      type: object
      title: CreateCommunityRequest
      description: Body of `POST /communities`. Phase 2.
      required:
        - name
      properties:
        name:
          type: string
          description: Community name.
        description:
          type: string
          description: Community description.
        groups:
          type: array
          description: Existing group ids to link at creation time.
          items:
            type: string
    UpdateCommunityRequest:
      type: object
      title: UpdateCommunityRequest
      description: Body of `PATCH /communities/{id}`. Phase 2.
      properties:
        name:
          type: string
          description: New community name.
        description:
          type: string
          description: New community description.
    LinkCommunityGroupRequest:
      type: object
      title: LinkCommunityGroupRequest
      description: Body of `POST /communities/{id}/groups`. Phase 2.
      required:
        - group_id
      properties:
        group_id:
          type: string
          description: Group to link into the community, ending in `@g.us`.
    CreateNewsletterRequest:
      type: object
      title: CreateNewsletterRequest
      description: Body of `POST /newsletters`.
      required:
        - name
      properties:
        name:
          type: string
          description: Channel name, 1-100 characters.
          maxLength: 100
        description:
          type: string
          description: Channel description shown to followers.
        picture:
          $ref: "#/components/schemas/MediaRef"
    TextStoryRequest:
      type: object
      title: TextStoryRequest
      description: Body of `POST /stories/text`.
      required:
        - body
      properties:
        body:
          type: string
          description: Status text, up to 700 characters.
          maxLength: 700
        background_color:
          type: string
          description: Background colour as `#RRGGBB`.
          pattern: ^#[0-9A-Fa-f]{6}$
        font:
          type: integer
          description: WhatsApp font index 0-5.
          minimum: 0
          maximum: 5
        contacts:
          type: array
          description: >-
            Contacts allowed to see the status, at most 256, people only (a group or Channel id is
            `invalid_recipient`). Defaults to every known contact.
          maxItems: 256
          items:
            $ref: "#/components/schemas/Recipient"
    MediaStoryRequest:
      type: object
      title: MediaStoryRequest
      description: Body of `POST /stories/media`.
      required:
        - media
      properties:
        media:
          $ref: "#/components/schemas/MediaRef"
        caption:
          type: string
          description: Caption shown over the image or video.
        contacts:
          type: array
          description: >-
            Contacts allowed to see the status, at most 256, people only (a group or Channel id is
            `invalid_recipient`). Defaults to every known contact.
          maxItems: 256
          items:
            $ref: "#/components/schemas/Recipient"
    AudioStoryRequest:
      type: object
      title: AudioStoryRequest
      description: Body of `POST /stories/audio`.
      required:
        - media
      properties:
        media:
          $ref: "#/components/schemas/MediaRef"
        background_color:
          type: string
          description: Background colour as `#RRGGBB`.
          pattern: ^#[0-9A-Fa-f]{6}$
        contacts:
          type: array
          description: >-
            Contacts allowed to see the status, at most 256, people only (a group or Channel id is
            `invalid_recipient`). Defaults to every known contact.
          maxItems: 256
          items:
            $ref: "#/components/schemas/Recipient"
    CreateLabelRequest:
      type: object
      title: CreateLabelRequest
      description: Body of `POST /labels`.
      required:
        - name
      properties:
        name:
          type: string
          description: Label text, up to 100 characters.
          maxLength: 100
        color:
          type: integer
          description: WhatsApp colour index 0-19. Defaults to 0, WhatsApp's first colour.
          minimum: 0
          maximum: 19
    UpdateLabelRequest:
      type: object
      title: UpdateLabelRequest
      description: Body of `PATCH /labels/{id}`.
      properties:
        name:
          type: string
          description: New label text.
        color:
          type: integer
          description: WhatsApp colour index 0-19.
          minimum: 0
          maximum: 19
    LabelAssociationRequest:
      type: object
      title: LabelAssociationRequest
      description: Body of the label association endpoints.
      required:
        - chat_id
      properties:
        chat_id:
          type: string
          description: Chat to attach the label to, in any accepted recipient form.
    UpdateBusinessProfileRequest:
      type: object
      title: UpdateBusinessProfileRequest
      description: Body of `PATCH /business/profile`. Phase 2.
      properties:
        description:
          type: string
          description: Business description.
        email:
          type: string
          description: Contact email.
          format: email
        website:
          type: array
          description: Websites to list on the profile.
          items:
            type: string
        address:
          type: string
          description: Street address.
        category:
          type: string
          description: Business category.
    CreateWebhookRequest:
      type: object
      title: CreateWebhookRequest
      description: Body of `POST /webhooks`.
      required:
        - url
        - events
      properties:
        url:
          type: string
          description: HTTPS endpoint that receives events. Private and loopback addresses are rejected.
          format: uri
        events:
          type: array
          description: "Events to subscribe to: exact names, prefix wildcards such as `messages.*`, or `*`."
          items:
            type: string
        enabled:
          type: boolean
          description: Start delivering immediately. Defaults to true.
        headers:
          type: object
          description: Extra headers sent with every delivery.
          additionalProperties:
            type: string
    UpdateWebhookRequest:
      type: object
      title: UpdateWebhookRequest
      description: Body of `PATCH /webhooks/{id}`. Only the fields you send are changed.
      properties:
        url:
          type: string
          description: New HTTPS endpoint.
          format: uri
        events:
          type: array
          description: Replacement event list.
          items:
            type: string
        enabled:
          type: boolean
          description: Pause or resume delivery.
        headers:
          type: object
          description: Replacement header map.
          additionalProperties:
            type: string
    TestWebhookRequest:
      type: object
      title: TestWebhookRequest
      description: Body of `POST /webhooks/{id}/test`.
      properties:
        event:
          type: string
          description: Event to build the sample payload from. Defaults to `messages`.
          enum:
            - messages
            - messages.edited
            - messages.deleted
            - messages.status
            - messages.reactions
            - chats
            - contacts
            - groups
            - groups.participants
            - presences
            - polls
            - calls
            - labels
            - channel
    WebhookTestResult:
      type: object
      title: WebhookTestResult
      description: What your endpoint answered to a synthetic delivery. Nothing is queued or retried.
      required:
        - delivered
        - status_code
        - duration_ms
        - error
        - response_excerpt
        - signature
      properties:
        delivered:
          type: boolean
          description: True when your endpoint answered with a 2xx status.
        status_code:
          type:
            - integer
            - "null"
          description: HTTP status your endpoint returned, or `null` when the request never completed.
        duration_ms:
          type: integer
          description: Round-trip time in milliseconds.
        error:
          type:
            - string
            - "null"
          description: Transport or validation error when `delivered` is false.
        response_excerpt:
          type:
            - string
            - "null"
          description: First 512 bytes of your response body.
        signature:
          type:
            - string
            - "null"
          description: The `X-Wapito-Signature` header that was sent, so you can replay the verification locally; `null`
            for a webhook without a secret.
    UploadMediaRequest:
      type: object
      title: UploadMediaRequest
      description: Multipart body of `POST /media`.
      required:
        - file
      properties:
        file:
          type: string
          description: File bytes.
          format: binary
        filename:
          type: string
          description: Override the stored file name.
    UploadMediaJsonRequest:
      type: object
      title: UploadMediaJsonRequest
      description: "JSON body of `POST /media`: the whole file as a `data:` URI, for clients that cannot build a
        multipart form."
      required:
        - data
      properties:
        data:
          type: string
          description: "`data:<mimetype>;base64,<bytes>`."
        mimetype:
          type: string
          description: Overrides the media type in the URI.
        filename:
          type: string
          description: File name shown to recipients.
    ContactCheckBatch:
      type: object
      title: ContactCheckBatch
      description: >-
        Result of a batch number check. Each number consumes one `chk` quota unit whether or not it exists.
      required:
        - checked
        - found
        - results
      properties:
        checked:
          type: integer
          description: Numbers submitted.
        found:
          type: integer
          description: Numbers registered on WhatsApp.
        results:
          type: array
          description: One entry per submitted number, in submission order.
          items:
            $ref: "#/components/schemas/ContactCheckResult"
    MessageList:
      type: object
      title: MessageList
      description: A page of messages, newest first.
      required:
        - messages
        - count
        - total
        - offset
      properties:
        messages:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Message"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    ChatList:
      type: object
      title: ChatList
      description: A page of chats, most recently active first.
      required:
        - chats
        - count
        - total
        - offset
      properties:
        chats:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Chat"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    ContactList:
      type: object
      title: ContactList
      description: A page of contacts.
      required:
        - contacts
        - count
        - total
        - offset
      properties:
        contacts:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Contact"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    GroupList:
      type: object
      title: GroupList
      description: A page of groups the channel belongs to.
      required:
        - groups
        - count
        - total
        - offset
      properties:
        groups:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Group"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    GroupParticipantList:
      type: object
      title: GroupParticipantList
      description: Participants of a group.
      required:
        - participants
        - count
      properties:
        participants:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/GroupParticipant"
        count:
          type: integer
          description: Number of items in this page.
    CommunityList:
      type: object
      title: CommunityList
      description: A page of communities. Phase 2.
      required:
        - communities
        - count
      properties:
        communities:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Community"
        count:
          type: integer
          description: Number of items in this page.
    NewsletterList:
      type: object
      title: NewsletterList
      description: A page of WhatsApp Channels.
      required:
        - newsletters
        - count
        - total
        - offset
      properties:
        newsletters:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Newsletter"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    StoryList:
      type: object
      title: StoryList
      description: Recent statuses. Phase 2.
      required:
        - stories
        - count
      properties:
        stories:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Story"
        count:
          type: integer
          description: Number of items in this page.
    LabelList:
      type: object
      title: LabelList
      description: Every label on the Business account.
      required:
        - labels
        - count
      properties:
        labels:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Label"
        count:
          type: integer
          description: Number of items in this page.
    WebhookList:
      type: object
      title: WebhookList
      description: Webhook subscriptions configured for the channel.
      required:
        - webhooks
        - count
      properties:
        webhooks:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/Webhook"
        count:
          type: integer
          description: Number of items in this page.
    PresenceList:
      type: object
      title: PresenceList
      description: Presence for every chat the channel is subscribed to.
      required:
        - presences
        - count
      properties:
        presences:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/PresenceInfo"
        count:
          type: integer
          description: Number of items in this page.
    CatalogProductList:
      type: object
      title: CatalogProductList
      description: A page of catalog products. Phase 2.
      required:
        - products
        - count
        - total
        - offset
      properties:
        products:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/CatalogProduct"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    BusinessOrderList:
      type: object
      title: BusinessOrderList
      description: A page of catalog orders. Phase 2.
      required:
        - orders
        - count
        - total
        - offset
      properties:
        orders:
          type: array
          description: Page of results.
          items:
            $ref: "#/components/schemas/BusinessOrder"
        count:
          type: integer
          description: Number of items in this page.
        total:
          type:
            - integer
            - "null"
          description: Total matches when the engine can count them, otherwise `null`.
        offset:
          type: integer
          description: Offset this page started at.
    WebhookEvent:
      type: object
      title: WebhookEvent
      description: >-
        Common envelope of every webhook delivery. The envelope never changes shape between events; only
        `data` does. Deliveries are at-least-once, so treat `id` (or the `X-Wapito-Delivery` header) as an
        idempotency key and ignore events you have already processed.
      required:
        - id
        - event
        - channel_id
        - timestamp
        - api_version
        - data
      properties:
        id:
          type: string
          description: Event id, `evt_` followed by a ULID. Stable across delivery retries.
        event:
          type: string
          description: Event name, identical to the `X-Wapito-Event` header.
        channel_id:
          type: string
          description: Channel that produced the event.
        timestamp:
          type: integer
          description: Unix epoch milliseconds the event was produced.
        api_version:
          type: string
          description: Schema version of the envelope.
          const: v1
        data:
          type: object
          description: Event-specific payload. See the concrete event schemas.
          additionalProperties: true
    MessagesEvent:
      title: MessagesEvent
      description: Webhook envelope for the `messages` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: messages
              description: Always `messages` for this delivery.
            data:
              $ref: "#/components/schemas/Message"
    MessagesEditedEvent:
      title: MessagesEditedEvent
      description: Webhook envelope for the `messages.edited` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: messages.edited
              description: Always `messages.edited` for this delivery.
            data:
              type: object
              description: The message and its new text.
              required:
                - message_id
                - chat_id
                - body
                - timestamp
              properties:
                message_id:
                  type: string
                  description: Id of the edited message.
                chat_id:
                  type: string
                  description: Chat the message belongs to.
                body:
                  type:
                    - string
                    - "null"
                  description: New text after the edit.
                timestamp:
                  type: integer
                  description: Unix epoch milliseconds of the edit.
    MessagesDeletedEvent:
      title: MessagesDeletedEvent
      description: Webhook envelope for the `messages.deleted` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: messages.deleted
              description: Always `messages.deleted` for this delivery.
            data:
              type: object
              description: The message that was revoked for everyone.
              required:
                - message_id
                - chat_id
                - from
                - timestamp
              properties:
                message_id:
                  type: string
                  description: Id of the deleted message.
                chat_id:
                  type: string
                  description: Chat the message belonged to.
                from:
                  type:
                    - string
                    - "null"
                  description: Who deleted it, in digits.
                timestamp:
                  type: integer
                  description: Unix epoch milliseconds of the deletion.
    MessagesStatusEvent:
      title: MessagesStatusEvent
      description: Webhook envelope for the `messages.status` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: messages.status
              description: Always `messages.status` for this delivery.
            data:
              type: object
              description: Delivery state change for a message you sent.
              required:
                - message_id
                - chat_id
                - recipient
                - status
                - ack
                - timestamp
              properties:
                message_id:
                  type: string
                  description: Id of the message whose state changed.
                chat_id:
                  type: string
                  description: Chat the message belongs to.
                recipient:
                  type:
                    - string
                    - "null"
                  description: Participant the state refers to; set in group chats.
                status:
                  type: string
                  description: New state.
                  enum:
                    - failed
                    - pending
                    - sent
                    - delivered
                    - read
                    - played
                ack:
                  type: integer
                  description: Raw WhatsApp ack code, -1 to 4, mapped one-to-one onto `status`.
                timestamp:
                  type: integer
                  description: Unix epoch milliseconds of the state change.
    MessagesReactionsEvent:
      title: MessagesReactionsEvent
      description: Webhook envelope for the `messages.reactions` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: messages.reactions
              description: Always `messages.reactions` for this delivery.
            data:
              type: object
              description: A reaction added to or removed from a message.
              required:
                - message_id
                - chat_id
                - from
                - emoji
                - timestamp
              properties:
                message_id:
                  type: string
                  description: Message that was reacted to.
                chat_id:
                  type: string
                  description: Chat the message belongs to.
                from:
                  type:
                    - string
                    - "null"
                  description: Who reacted, in digits.
                emoji:
                  type: string
                  description: The emoji, or an empty string when the reaction was removed.
                timestamp:
                  type: integer
                  description: Unix epoch milliseconds of the reaction.
    ChatsEvent:
      title: ChatsEvent
      description: Webhook envelope for the `chats` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: chats
              description: Always `chats` for this delivery.
            data:
              type: object
              description: A chat-level change. Today only archive and unarchive are reported.
              required:
                - action
                - chat_id
                - archived
              properties:
                action:
                  type: string
                  description: What changed.
                  enum:
                    - archive
                chat_id:
                  type: string
                  description: Chat that changed.
                archived:
                  type: boolean
                  description: True when the chat moved into the archive.
    ContactsEvent:
      title: ContactsEvent
      description: Webhook envelope for the `contacts` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: contacts
              description: Always `contacts` for this delivery.
            data:
              type: object
              description: >-
                A contact Wapito learned about, typically on first inbound message or once a LID resolved to a
                phone number.
              required:
                - action
                - id
                - phone
                - lid
                - name
              properties:
                action:
                  type: string
                  description: "`new` the first time a contact writes in, `lid_resolved` when a LID-only sender is matched to a
                    phone number."
                  enum:
                    - new
                    - lid_resolved
                id:
                  type: string
                  description: Contact id in `@s.whatsapp.net` form.
                phone:
                  type:
                    - string
                    - "null"
                  description: Phone number in digits once known.
                lid:
                  type:
                    - string
                    - "null"
                  description: LID when WhatsApp addressed the contact by LID.
                name:
                  type:
                    - string
                    - "null"
                  description: Best known display name.
    GroupsEvent:
      title: GroupsEvent
      description: Webhook envelope for the `groups` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: groups
              description: Always `groups` for this delivery.
            data:
              type: object
              description: The channel joined or left a group, or group metadata changed.
              required:
                - action
                - group_id
                - subject
                - description
                - picture
              properties:
                action:
                  type: string
                  description: What happened.
                  enum:
                    - joined
                    - left
                    - update
                group_id:
                  type: string
                  description: Group that changed.
                subject:
                  type:
                    - string
                    - "null"
                  description: Group name after the change.
                description:
                  type:
                    - string
                    - "null"
                  description: Group description after the change.
                picture:
                  type:
                    - string
                    - "null"
                  description: Group icon URL after the change.
    GroupsParticipantsEvent:
      title: GroupsParticipantsEvent
      description: Webhook envelope for the `groups.participants` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: groups.participants
              description: Always `groups.participants` for this delivery.
            data:
              type: object
              description: Membership or role change in a group, including join requests waiting for approval.
              required:
                - group_id
                - action
                - participants
                - by
              properties:
                group_id:
                  type: string
                  description: Group that changed.
                action:
                  type: string
                  description: What happened to the listed participants.
                  enum:
                    - add
                    - remove
                    - promote
                    - demote
                    - join_request
                participants:
                  type: array
                  description: Participants affected by this change.
                  items:
                    type: object
                    description: One affected participant.
                    required:
                      - id
                      - phone
                      - role
                    properties:
                      id:
                        type: string
                        description: Participant id in `@s.whatsapp.net` form.
                      phone:
                        type:
                          - string
                          - "null"
                        description: Phone number in digits.
                      role:
                        type:
                          - string
                          - "null"
                        description: Role after the change.
                        enum:
                          - member
                          - admin
                          - superadmin
                by:
                  type:
                    - string
                    - "null"
                  description: Who performed the change, in digits.
    PresencesEvent:
      title: PresencesEvent
      description: Webhook envelope for the `presences` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: presences
              description: Always `presences` for this delivery.
            data:
              type: object
              description: Presence update for a chat you subscribed to with `POST /presence/{chat_id}/subscribe`.
              required:
                - chat_id
                - presence
                - last_seen
              properties:
                chat_id:
                  type: string
                  description: Chat the presence belongs to.
                presence:
                  type: string
                  description: New presence state.
                  enum:
                    - online
                    - offline
                    - typing
                    - recording
                    - paused
                last_seen:
                  type:
                    - integer
                    - "null"
                  description: Unix epoch milliseconds the contact was last online, when they publish it.
    PollsEvent:
      title: PollsEvent
      description: Webhook envelope for the `polls` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: polls
              description: Always `polls` for this delivery.
            data:
              type: object
              description: Someone voted in a poll you sent, or the vote could not be decrypted.
              required:
                - poll_message_id
                - chat_id
                - voter
                - selected_options
                - failed
              properties:
                poll_message_id:
                  type: string
                  description: Id of the poll message.
                chat_id:
                  type: string
                  description: Chat the poll lives in.
                voter:
                  type:
                    - string
                    - "null"
                  description: Who voted, in digits.
                selected_options:
                  type: array
                  description: Options the voter currently has selected; an empty array means they cleared their vote.
                  items:
                    type: string
                failed:
                  type: boolean
                  description: >-
                    True when the vote arrived but could not be decrypted, usually because the poll was sent
                    before this channel connected.
    CallsEvent:
      title: CallsEvent
      description: Webhook envelope for the `calls` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: calls
              description: Always `calls` for this delivery.
            data:
              type: object
              description: An incoming WhatsApp call was offered, accepted or rejected.
              required:
                - call_id
                - from
                - status
                - is_video
                - is_group
              properties:
                call_id:
                  type: string
                  description: Call id to pass to `POST /calls/reject`.
                from:
                  type:
                    - string
                    - "null"
                  description: Caller, in digits.
                status:
                  type: string
                  description: Call state.
                  enum:
                    - received
                    - accepted
                    - rejected
                is_video:
                  type: boolean
                  description: True for a video call.
                is_group:
                  type: boolean
                  description: True for a group call.
    LabelsEvent:
      title: LabelsEvent
      description: Webhook envelope for the `labels` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: labels
              description: Always `labels` for this delivery.
            data:
              type: object
              description: A Business label was created, renamed, deleted, or attached to or detached from a chat.
              required:
                - action
                - label
                - chat_id
              properties:
                action:
                  type: string
                  description: What happened.
                  enum:
                    - upsert
                    - deleted
                    - chat_added
                    - chat_deleted
                label:
                  type:
                    - object
                    - "null"
                  description: The label involved.
                  required:
                    - id
                    - name
                    - color
                    - color_hex
                  properties:
                    id:
                      type: string
                      description: Label id.
                    name:
                      type:
                        - string
                        - "null"
                      description: Label text; `null` on the chat association events, where the engine sends only the id.
                    color:
                      type:
                        - integer
                        - "null"
                      description: WhatsApp colour index 0-19.
                    color_hex:
                      type:
                        - string
                        - "null"
                      description: Hex rendering of `color`, when the engine sends one.
                chat_id:
                  type:
                    - string
                    - "null"
                  description: Chat involved, for the `chat_added` and `chat_deleted` actions.
    ChannelEvent:
      title: ChannelEvent
      description: Webhook envelope for the `channel` event.
      allOf:
        - $ref: "#/components/schemas/WebhookEvent"
        - type: object
          properties:
            event:
              type: string
              const: channel
              description: Always `channel` for this delivery.
            data:
              type: object
              description: >-
                The channel changed connection state. This is the only event that still fires when WhatsApp is
                unreachable, so alert on it.
              required:
                - status
                - previous_status
                - phone
              properties:
                status:
                  type: string
                  description: New state.
                  enum:
                    - created
                    - qr
                    - pairing
                    - connected
                    - disconnected
                    - logged_out
                    - banned
                previous_status:
                  type: string
                  description: State the channel was in before.
                  enum:
                    - created
                    - qr
                    - pairing
                    - connected
                    - disconnected
                    - logged_out
                    - banned
                phone:
                  type:
                    - string
                    - "null"
                  description: Linked phone number in digits, when known.
                reason:
                  type: string
                  description: Why the state changed.
                  enum:
                    - banned
                    - logged_out
                    - user_logout
                    - engine_failed
  responses:
    BadRequest:
      description: >-
        The request body or query string is malformed, or the recipient cannot be parsed into a WhatsApp id.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            invalid_request:
              summary: invalid_request — schema validation failed
              value:
                error:
                  code: invalid_request
                  message: The request payload failed validation.
                  details:
                    issues:
                      - path: body.options
                        message: Array must contain at least 2 element(s)
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            invalid_recipient:
              summary: invalid_recipient — `to` is not a dialable number or valid WhatsApp id
              value:
                error:
                  code: invalid_recipient
                  message: The recipient is not a valid WhatsApp address.
                  details:
                    to: "+1555"
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    Unauthorized:
      description: The channel token is missing, malformed, revoked or belongs to a deleted channel.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            unauthorized:
              summary: unauthorized — no or unusable Bearer token
              value:
                error:
                  code: unauthorized
                  message: Missing or invalid channel token.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            token_revoked:
              summary: token_revoked — the token was rotated in the dashboard
              value:
                error:
                  code: token_revoked
                  message: This channel token has been revoked.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    PaymentRequired:
      description: >-
        A cap that a higher plan lifts was reached (Sandbox: one channel, two webhooks, no proxy).
        `details.checkout_url` is the dashboard page that starts the upgrade; `details.plan` names the
        plan that refused, `details.limit` the cap it applied (for counted features) and
        `details.price_ids` the Stripe prices, so a client can start Checkout itself. A cap no plan
        lifts (five webhooks on Premium) is `429 quota_exceeded` instead.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            plan_required:
              summary: plan_required — upgrade needed, `checkout_url` included
              value:
                error:
                  code: plan_required
                  message: This action requires a Premium channel.
                  details:
                    feature: webhooks
                    plan: sandbox
                    limit: 2
                    checkout_url: https://wapito.com/app/?upgrade=1
                    price_ids:
                      monthly: price_1S0aBcDeFgHiJkLmMONTHLY
                      yearly: price_1S0aBcDeFgHiJkLmYEARLY
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    Forbidden:
      description: The token is valid but the channel may not perform this action right now.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            forbidden:
              summary: forbidden — the channel does not own the target object
              value:
                error:
                  code: forbidden
                  message: You are not allowed to perform this action.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            channel_locked:
              summary: channel_locked — billing lapsed or the channel was locked by an operator
              value:
                error:
                  code: channel_locked
                  message: This channel is locked.
                  details:
                    reason: plan_required
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    NotFound:
      description: The addressed object does not exist, or the number is not on WhatsApp.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            not_found:
              summary: not_found — unknown id
              value:
                error:
                  code: not_found
                  message: The requested resource does not exist.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            message_not_found:
              summary: message_not_found — the message id is unknown to the engine
              value:
                error:
                  code: message_not_found
                  message: The requested message does not exist.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            not_on_whatsapp:
              summary: not_on_whatsapp — the number is not registered
              value:
                error:
                  code: not_on_whatsapp
                  message: This number is not registered on WhatsApp.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    Conflict:
      description: >-
        The channel is in the wrong state for this action, or the account type does not support it.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            channel_not_connected:
              summary: channel_not_connected — link the number first
              value:
                error:
                  code: channel_not_connected
                  message: The channel is not connected.
                  details:
                    status: qr
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            channel_not_in_qr_state:
              summary: channel_not_in_qr_state — no QR available while the session boots
              value:
                error:
                  code: channel_not_in_qr_state
                  message: The channel is not waiting for a QR scan.
                  details:
                    status: created
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            business_account_required:
              summary: business_account_required — labels need WhatsApp Business
              value:
                error:
                  code: business_account_required
                  message: This action requires a WhatsApp Business account.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    PayloadTooLarge:
      description: The upload exceeds the media size cap for the plan.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            payload_too_large:
              summary: payload_too_large — 16 MB on sandbox, 64 MB on premium
              value:
                error:
                  code: payload_too_large
                  message: The request payload is too large.
                  details:
                    max_bytes: 16777216
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    UnsupportedMediaType:
      description: The file type cannot be sent as the requested message kind.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            unsupported_media_type:
              summary: unsupported_media_type — wrong media type for the endpoint
              value:
                error:
                  code: unsupported_media_type
                  message: This media type is not supported.
                  details:
                    mimetype: application/x-msdownload
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    UnprocessableEntity:
      description: The request is well formed but cannot be acted on.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            webhook_url_invalid:
              summary: webhook_url_invalid — not HTTPS, or resolves to a private address
              value:
                error:
                  code: webhook_url_invalid
                  message: The webhook URL is not reachable or not allowed.
                  details:
                    url: http://localhost:3000/hook
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    Locked:
      description: WhatsApp has temporarily blocked outbound first contact from this number.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            reachout_timelocked:
              summary: reachout_timelocked — wait until `details.until`
              value:
                error:
                  code: reachout_timelocked
                  message: WhatsApp has temporarily locked reach-outs for this channel.
                  details:
                    until: "2026-09-15T14:00:00.000Z"
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    TooManyRequests:
      description: >-
        A rate limit, a plan quota or one of the anti-ban guards stopped the request. Every one of these is
        safe to retry later; read `Retry-After` when present.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            rate_limited:
              summary: rate_limited — per-minute API rate limit
              value:
                error:
                  code: rate_limited
                  message: Too many requests.
                  details:
                    retry_after: 12
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            quota_exceeded:
              summary: quota_exceeded — plan quota for the day or month
              value:
                error:
                  code: quota_exceeded
                  message: The plan quota for this resource is exhausted.
                  details:
                    quota: sent
                    used: 150
                    resets_at: "2026-09-16T00:00:00.000Z"
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            send_rate_limited:
              summary: send_rate_limited — the send queue did not drain within 20 s
              value:
                error:
                  code: send_rate_limited
                  message: The send queue for this channel is saturated.
                  details:
                    retry_after: 5
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            cold_send_limit:
              summary: cold_send_limit — too many first messages to new recipients this hour
              value:
                error:
                  code: cold_send_limit
                  message: The hourly limit for messages to new recipients is reached.
                  details:
                    window_cap: 20
                    resets_at: "2026-09-15T09:00:00.000Z"
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            warmup_limit:
              summary: warmup_limit — the warm-up ladder cap for today
              value:
                error:
                  code: warmup_limit
                  message: The warm-up limit for this channel is reached.
                  details:
                    day: 2
                    cap: 200
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    NotImplemented:
      description: >-
        The feature is not available. `engine_unsupported_feature` means the other engine can do it and
        `details.hint` tells you how to switch; `feature_not_available` means no engine supports it yet.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            engine_unsupported_feature:
              summary: engine_unsupported_feature — supported on the other engine
              value:
                error:
                  code: engine_unsupported_feature
                  message: The engine of this channel does not support that feature.
                  details:
                    hint: Switch this channel to the noweb engine in the dashboard.
                    supported_engines:
                      - noweb
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
            feature_not_available:
              summary: feature_not_available — phase 2, no engine support
              value:
                error:
                  code: feature_not_available
                  message: This feature is not available.
                  details:
                    phase: 2
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    BadGateway:
      description: The engine answered with an error Wapito could not translate into a more specific code.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            engine_error:
              summary: engine_error — unexpected engine failure
              value:
                error:
                  code: engine_error
                  message: The WhatsApp engine returned an error.
                  details:
                    engine: gows
                    status: 500
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    ServiceUnavailable:
      description: The engine session is not reachable. Retry with backoff.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            engine_unavailable:
              summary: engine_unavailable — engine down or restarting
              value:
                error:
                  code: engine_unavailable
                  message: The WhatsApp engine is unavailable.
                  details:
                    engine: gows
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    GatewayTimeout:
      description: The engine did not answer before the upstream timeout.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            engine_timeout:
              summary: engine_timeout — no answer within the deadline
              value:
                error:
                  code: engine_timeout
                  message: The WhatsApp engine did not respond in time.
                  details:
                    timeout_ms: 20000
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
    InternalError:
      description: Something went wrong inside Wapito. Quote `request_id` when reporting it.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            internal_error:
              summary: internal_error — unexpected failure
              value:
                error:
                  code: internal_error
                  message: Something went wrong on our side.
                  request_id: req_01JRQ8F4X9N2K7YB3C5V6W8H0T
