openapi: 3.1.0
info:
  title: wasender.dev API
  version: 1.0.0
  summary: WhatsApp API Gateway for developers
  description: >
    Send and receive WhatsApp messages over plain HTTP. Connect a number by
    scanning a QR

    code, then use the channel token that comes back to call any endpoint below.


    ## Authentication


    Every request is authenticated with a **channel token** — one token per
    connected

    WhatsApp number. Pass it either way:


    ```

    Authorization: Bearer wsk_sg_xxxxxxxxxxxxxxxxxxxxxxxx

    ```

    ```

    GET /chats?token=wsk_sg_xxxxxxxxxxxxxxxxxxxxxxxx

    ```


    The token encodes its region (`wsk_sg_…`, `wsk_us_…`, `wsk_eu_…`) but is
    otherwise opaque.

    Treat it as a password: it grants full control of the number it belongs to.


    ## Regions


    Sessions currently run in **Singapore**, serving customers worldwide.
    Sending a WhatsApp

    message is asynchronous — delivery goes through Meta's own global network,
    not ours — so

    the region affects only the latency of your API call, not how fast your
    recipients get

    messages.


    A channel is pinned to its region at creation and stays there: moving a live
    session

    changes its outbound IP, which materially raises the risk of WhatsApp
    restricting the

    number. Additional regions will be added for data-residency requirements.


    ## Compatibility


    The endpoints below are request- and response-compatible with whapi.cloud,
    so migrating

    is a change of base URL and token. Endpoints outside this document return
    `501` with an

    error body naming the endpoint; open an issue if you need one.


    ## Important


    This service drives WhatsApp through an unofficial client, the same
    mechanism as

    WhatsApp Web. Meta does not sanction it. Use a dedicated number you can
    afford to lose,

    do not send first-contact messages to people who have not opted in, and keep
    a fallback

    channel for anything auth- or revenue-critical. For regulated workloads, use
    Meta's

    official Cloud API instead.
  contact:
    name: wasender.dev support
    url: https://wasender.dev/support
    email: support@wasender.dev
  license:
    name: Proprietary
    url: https://wasender.dev/terms
servers:
  - url: https://api.wasender.dev
    description: Production
security:
  - bearerAuth: []
  - tokenAuth: []
tags:
  - name: Channel
    description: The connected WhatsApp number itself — health, settings, profile, logout.
  - name: Messages
    description: Send, read, edit, react to and delete messages.
  - name: Chats
    description: Conversations, whether one-to-one or group.
  - name: Contacts
    description: Address book and WhatsApp-reachability checks.
  - name: Groups
    description: Create and administer groups.
  - name: Media
    description: Upload and retrieve files.
  - name: Presences
    description: Online status and typing indicators.
paths:
  /health:
    get:
      tags:
        - Channel
      operationId: checkHealth
      summary: Check channel health
      description: >
        Reports whether the number is connected and ready to send. Safe to poll
        — it does not

        touch WhatsApp, only the gateway's view of the session.


        A `hibernated` channel is healthy: it has simply been parked to save
        resources and

        will wake on the next request that needs the socket, taking a few
        seconds to do so.
      parameters:
        - name: wakeup
          in: query
          description: Wake a hibernated channel and wait for it to reconnect before
            answering.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Channel status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Health"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /settings:
    get:
      tags:
        - Channel
      operationId: getSettings
      summary: Get channel settings
      responses:
        "200":
          description: Current settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Settings"
        "401":
          $ref: "#/components/responses/Unauthorized"
    patch:
      tags:
        - Channel
      operationId: updateSettings
      summary: Update channel settings
      description: >
        Sets the webhook the channel delivers events to, and the automatic
        behaviours

        (read receipts, presence) applied to incoming messages.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Settings"
            example:
              webhooks:
                - url: https://example.com/wasender/webhook
                  events:
                    - type: messages
                      method: post
                  mode: body
      responses:
        "200":
          description: Updated settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Settings"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /users/login/image:
    get:
      tags:
        - Channel
      operationId: getLoginQr
      summary: Get the QR code to link this number
      description: >
        Returns the QR code to scan from WhatsApp on the phone

        (**Settings → Linked devices → Link a device**).


        QR codes rotate roughly every 20 seconds; re-request when one expires.
        Once scanned,

        credentials are stored and future reconnects need no scan.
      parameters:
        - name: wakeup
          in: query
          schema:
            type: boolean
            default: true
        - name: size
          in: query
          description: Image edge length in pixels.
          schema:
            type: integer
            default: 300
            minimum: 100
            maximum: 1000
      responses:
        "200":
          description: QR code image
          content:
            image/png:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          description: Channel is already connected, so there is nothing to scan.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResponseError"
  /users/profile:
    get:
      tags:
        - Channel
      operationId: getProfile
      summary: Get this number's own profile
      responses:
        "200":
          description: Profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "401":
          $ref: "#/components/responses/Unauthorized"
    patch:
      tags:
        - Channel
      operationId: updateProfile
      summary: Update this number's own profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Display name
                status:
                  type: string
                  description: The "about" line
                profile_pic:
                  type: string
                  description: Base64 image, or a URL to fetch one from.
      responses:
        "200":
          description: Updated profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /users/logout:
    post:
      tags:
        - Channel
      operationId: logoutUser
      summary: Unlink this number
      description: >
        Logs the device out of WhatsApp and discards the stored credentials. The
        channel

        itself is kept — reconnecting means scanning a fresh QR code.


        This does **not** cancel billing. Delete the channel from the dashboard
        or the

        management API to stop being charged for it.
      responses:
        "200":
          description: Logged out
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/list:
    get:
      tags:
        - Messages
      operationId: getMessages
      summary: List messages across all chats
      parameters:
        - $ref: "#/components/parameters/Count"
        - $ref: "#/components/parameters/Offset"
        - name: time_from
          in: query
          description: Only messages at or after this Unix timestamp (seconds).
          schema:
            type: integer
        - name: time_to
          in: query
          description: Only messages at or before this Unix timestamp (seconds).
          schema:
            type: integer
        - name: normal_types
          in: query
          description: Exclude system and protocol messages.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessagesList"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/list/{ChatID}:
    get:
      tags:
        - Messages
      operationId: getMessagesByChatId
      summary: List messages in one chat
      parameters:
        - $ref: "#/components/parameters/ChatID"
        - $ref: "#/components/parameters/Count"
        - $ref: "#/components/parameters/Offset"
        - name: time_from
          in: query
          schema:
            type: integer
        - name: time_to
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessagesList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /messages/text:
    post:
      tags:
        - Messages
      operationId: sendMessageText
      summary: Send a text message
      description: >
        The workhorse endpoint.


        `to` accepts a bare phone number in international format without `+`
        (`61371989950`)

        or a full chat id (`61371989950@s.whatsapp.net`, `1234567890@g.us` for a
        group).


        A first-ever message to someone who has never messaged you is the single
        most

        reliable way to get a number restricted. Send to people who expect to
        hear from you.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SenderText"
            examples:
              simple:
                summary: Plain text
                value:
                  to: "61371989950"
                  body: Hello from wasender.dev!
              reply:
                summary: Reply to a message, typing first
                value:
                  to: "61371989950"
                  body: On it — give me two minutes.
                  quoted: ABGGFlA5FpafAgo6tHcNPtFw6r0
                  typing_time: 3
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/ChannelUnavailable"
  /messages/image:
    post:
      tags:
        - Messages
      operationId: sendMessageImage
      summary: Send an image
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SenderMedia"
            example:
              to: "61371989950"
              media: https://example.com/photo.jpg
              caption: Here is the photo you asked for
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SenderMediaUpload"
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /messages/video:
    post:
      tags:
        - Messages
      operationId: sendMessageVideo
      summary: Send a video
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SenderMedia"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SenderMediaUpload"
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
  /messages/audio:
    post:
      tags:
        - Messages
      operationId: sendMessageAudio
      summary: Send an audio file
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SenderMedia"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SenderMediaUpload"
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/voice:
    post:
      tags:
        - Messages
      operationId: sendMessageVoice
      summary: Send a voice note
      description: >
        Renders as a push-to-play voice note rather than an audio attachment.
        WhatsApp expects

        Opus in an Ogg container; other formats are transcoded, which adds
        latency.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SenderMedia"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SenderMediaUpload"
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/document:
    post:
      tags:
        - Messages
      operationId: sendMessageDocument
      summary: Send a document
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/SenderMedia"
                - type: object
                  properties:
                    filename:
                      type: string
                      description: Name shown to the recipient. Defaults to the source filename.
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SenderMediaUpload"
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
  /messages/sticker:
    post:
      tags:
        - Messages
      operationId: sendMessageSticker
      summary: Send a sticker
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SenderMedia"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SenderMediaUpload"
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/location:
    post:
      tags:
        - Messages
      operationId: sendMessageLocation
      summary: Send a location
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/Sender"
                - type: object
                  required:
                    - latitude
                    - longitude
                  properties:
                    latitude:
                      type: number
                      format: double
                      example: 1.3521
                    longitude:
                      type: number
                      format: double
                      example: 103.8198
                    name:
                      type: string
                      description: Place name
                    address:
                      type: string
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/contact:
    post:
      tags:
        - Messages
      operationId: sendMessageContact
      summary: Send a contact card
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/Sender"
                - type: object
                  required:
                    - name
                    - vcard
                  properties:
                    name:
                      type: string
                    vcard:
                      type: string
                      description: vCard 3.0 payload
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/link_preview:
    post:
      tags:
        - Messages
      operationId: sendMessageLinkPreview
      summary: Send a link with a rich preview
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/Sender"
                - type: object
                  required:
                    - body
                  properties:
                    body:
                      type: string
                      description: Text containing the URL
                    title:
                      type: string
                    description:
                      type: string
                    preview:
                      type: string
                      description: Thumbnail URL or base64 image
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/poll:
    post:
      tags:
        - Messages
      operationId: sendMessagePoll
      summary: Send a poll
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/Sender"
                - type: object
                  required:
                    - title
                    - options
                  properties:
                    title:
                      type: string
                      description: The question
                    options:
                      type: array
                      minItems: 2
                      maxItems: 12
                      items:
                        type: string
                    count:
                      type: integer
                      description: How many options a voter may pick.
                      default: 1
                      minimum: 1
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/{MessageID}:
    get:
      tags:
        - Messages
      operationId: getMessage
      summary: Get one message
      parameters:
        - $ref: "#/components/parameters/MessageID"
      responses:
        "200":
          description: Message
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      tags:
        - Messages
      operationId: markMessageAsRead
      summary: Mark a message as read
      parameters:
        - $ref: "#/components/parameters/MessageID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      tags:
        - Messages
      operationId: editMessage
      summary: Edit a message
      description: WhatsApp only allows editing your own messages, within 15 minutes
        of sending.
      parameters:
        - $ref: "#/components/parameters/MessageID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - body
              properties:
                body:
                  type: string
      responses:
        "200":
          $ref: "#/components/responses/SentMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      tags:
        - Messages
      operationId: deleteMessage
      summary: Delete a message
      parameters:
        - $ref: "#/components/parameters/MessageID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /messages/{MessageID}/reaction:
    put:
      tags:
        - Messages
      operationId: reactToMessage
      summary: React to a message
      parameters:
        - $ref: "#/components/parameters/MessageID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                emoji:
                  type: string
                  description: A single emoji. Send an empty string to remove your reaction.
                  example: 👍
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    delete:
      tags:
        - Messages
      operationId: removeReaction
      summary: Remove your reaction
      parameters:
        - $ref: "#/components/parameters/MessageID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /messages/{MessageID}/star:
    put:
      tags:
        - Messages
      operationId: starMessage
      summary: Star or unstar a message
      description: >
        **Not supported.** Returns `501`. The underlying engine exposes no way
        to star a

        message, and silently accepting the call would drop the flag without
        telling you.
      parameters:
        - $ref: "#/components/parameters/MessageID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - star
              properties:
                star:
                  type: boolean
      responses:
        "401":
          $ref: "#/components/responses/Unauthorized"
        "501":
          description: Not supported by this service
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResponseError"
  /statuses/{MessageID}:
    get:
      tags:
        - Messages
      operationId: getMessageStatuses
      summary: Get delivery and read receipts for a message
      parameters:
        - $ref: "#/components/parameters/MessageID"
      responses:
        "200":
          description: Per-recipient receipts
          content:
            application/json:
              schema:
                type: object
                properties:
                  statuses:
                    type: array
                    items:
                      $ref: "#/components/schemas/Status"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /chats:
    get:
      tags:
        - Chats
      operationId: getChats
      summary: List chats
      parameters:
        - $ref: "#/components/parameters/Count"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Chats
          content:
            application/json:
              schema:
                type: object
                properties:
                  chats:
                    type: array
                    items:
                      $ref: "#/components/schemas/Chat"
                  count:
                    type: integer
                  total:
                    type: integer
                  offset:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
  /chats/{ChatID}:
    get:
      tags:
        - Chats
      operationId: getChat
      summary: Get one chat
      parameters:
        - $ref: "#/components/parameters/ChatID"
      responses:
        "200":
          description: Chat
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chat"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      tags:
        - Chats
      operationId: markChatAsRead
      summary: Mark a chat as read
      parameters:
        - $ref: "#/components/parameters/ChatID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
    patch:
      tags:
        - Chats
      operationId: updateChat
      summary: Archive, pin or mute a chat
      parameters:
        - $ref: "#/components/parameters/ChatID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                archive:
                  type: boolean
                pin:
                  type: boolean
                read:
                  type: boolean
                mute_until:
                  type: integer
                  description: Unix timestamp (seconds). `0` unmutes; `-1` mutes indefinitely.
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    delete:
      tags:
        - Chats
      operationId: deleteChat
      summary: Delete a chat
      parameters:
        - $ref: "#/components/parameters/ChatID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /contacts:
    get:
      tags:
        - Contacts
      operationId: getContacts
      summary: List contacts
      parameters:
        - $ref: "#/components/parameters/Count"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Contacts
          content:
            application/json:
              schema:
                type: object
                properties:
                  contacts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Contact"
                  count:
                    type: integer
                  total:
                    type: integer
                  offset:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - Contacts
      operationId: checkPhones
      summary: Check whether numbers are on WhatsApp
      description: >
        Validates numbers before you message them. Cheap insurance: sending to
        numbers that

        are not on WhatsApp is a strong spam signal and a common cause of
        restriction.


        Keep batches modest — a few hundred at a time, not tens of thousands.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - contacts
              properties:
                contacts:
                  type: array
                  maxItems: 500
                  items:
                    type: string
                  example:
                    - "61371989950"
                    - "61371989951"
                blocking:
                  type: string
                  enum:
                    - no_wait
                    - wait
                  default: wait
      responses:
        "200":
          description: Reachability per number
          content:
            application/json:
              schema:
                type: object
                properties:
                  contacts:
                    type: array
                    items:
                      type: object
                      properties:
                        input:
                          type: string
                        status:
                          type: string
                          enum:
                            - valid
                            - invalid
                            - processing
                        wa_id:
                          type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /contacts/{ContactID}:
    get:
      tags:
        - Contacts
      operationId: getContact
      summary: Get one contact
      parameters:
        - $ref: "#/components/parameters/ContactID"
      responses:
        "200":
          description: Contact
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /contacts/{ContactID}/profile:
    get:
      tags:
        - Contacts
      operationId: getContactProfile
      summary: Get a contact's public profile
      parameters:
        - $ref: "#/components/parameters/ContactID"
      responses:
        "200":
          description: Public profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /groups:
    get:
      tags:
        - Groups
      operationId: getGroups
      summary: List groups
      parameters:
        - $ref: "#/components/parameters/Count"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Groups
          content:
            application/json:
              schema:
                type: object
                properties:
                  groups:
                    type: array
                    items:
                      $ref: "#/components/schemas/Group"
                  count:
                    type: integer
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - Groups
      operationId: createGroup
      summary: Create a group
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - subject
                - participants
              properties:
                subject:
                  type: string
                  description: Group name
                participants:
                  type: array
                  minItems: 1
                  items:
                    type: string
      responses:
        "200":
          description: Created group
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /groups/{GroupID}:
    get:
      tags:
        - Groups
      operationId: getGroup
      summary: Get one group
      parameters:
        - $ref: "#/components/parameters/GroupID"
      responses:
        "200":
          description: Group
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags:
        - Groups
      operationId: updateGroup
      summary: Update a group's subject or description
      parameters:
        - $ref: "#/components/parameters/GroupID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                subject:
                  type: string
                description:
                  type: string
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    delete:
      tags:
        - Groups
      operationId: leaveGroup
      summary: Leave a group
      parameters:
        - $ref: "#/components/parameters/GroupID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /groups/{GroupID}/participants:
    post:
      tags:
        - Groups
      operationId: addGroupParticipant
      summary: Add participants
      description: >
        WhatsApp privacy settings let people refuse group adds. Participants who
        cannot be

        added directly are returned with an invite link to send them instead.
      parameters:
        - $ref: "#/components/parameters/GroupID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - participants
              properties:
                participants:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: Per-participant result
          content:
            application/json:
              schema:
                type: object
                properties:
                  participants:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        status:
                          type: string
                        invite_link:
                          type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    delete:
      tags:
        - Groups
      operationId: removeGroupParticipant
      summary: Remove participants
      parameters:
        - $ref: "#/components/parameters/GroupID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - participants
              properties:
                participants:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /groups/{GroupID}/admins:
    patch:
      tags:
        - Groups
      operationId: promoteGroupAdmins
      summary: Promote participants to admin
      parameters:
        - $ref: "#/components/parameters/GroupID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - participants
              properties:
                participants:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    delete:
      tags:
        - Groups
      operationId: demoteGroupAdmins
      summary: Demote admins to member
      parameters:
        - $ref: "#/components/parameters/GroupID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - participants
              properties:
                participants:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /groups/link/{InviteCode}:
    post:
      tags:
        - Groups
      operationId: joinGroupByCode
      summary: Join a group with an invite code
      description: >
        Accepts the code from an invite link — the part after
        `chat.whatsapp.com/`.
      parameters:
        - name: InviteCode
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /groups/{GroupID}/invite:
    get:
      tags:
        - Groups
      operationId: getGroupInvite
      summary: Get the group invite link
      parameters:
        - $ref: "#/components/parameters/GroupID"
      responses:
        "200":
          description: Invite link
          content:
            application/json:
              schema:
                type: object
                properties:
                  invite_code:
                    type: string
                  invite_link:
                    type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    delete:
      tags:
        - Groups
      operationId: revokeGroupInvite
      summary: Revoke and regenerate the invite link
      parameters:
        - $ref: "#/components/parameters/GroupID"
      responses:
        "200":
          description: New invite link
          content:
            application/json:
              schema:
                type: object
                properties:
                  invite_code:
                    type: string
                  invite_link:
                    type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /media:
    get:
      tags:
        - Media
      operationId: getMediaFiles
      summary: List uploaded media
      parameters:
        - $ref: "#/components/parameters/Count"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Media files
          content:
            application/json:
              schema:
                type: object
                properties:
                  media:
                    type: array
                    items:
                      $ref: "#/components/schemas/MediaFile"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - Media
      operationId: uploadMedia
      summary: Upload a file
      description: >
        Upload once and reuse the returned `id` across many sends — much cheaper
        than

        re-uploading the same file per recipient.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - media
              properties:
                media:
                  type: string
                  format: binary
      responses:
        "200":
          description: Uploaded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaFile"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
  /media/{MediaID}:
    get:
      tags:
        - Media
      operationId: getMedia
      summary: Download a media file
      parameters:
        - $ref: "#/components/parameters/MediaID"
      responses:
        "200":
          description: File contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      tags:
        - Media
      operationId: deleteMedia
      summary: Delete a media file
      parameters:
        - $ref: "#/components/parameters/MediaID"
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /presences/me:
    put:
      tags:
        - Presences
      operationId: setMyPresence
      summary: Set your own online presence
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - presence
              properties:
                presence:
                  type: string
                  enum:
                    - online
                    - offline
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /presences/{EntryID}:
    post:
      tags:
        - Presences
      operationId: subscribeToPresence
      summary: Subscribe to a contact's presence
      description: >
        **Not supported.** Returns `501`. Presence updates arrive through
        webhooks rather than

        per-contact subscriptions, so there is nothing to subscribe to.
      parameters:
        - name: EntryID
          in: path
          required: true
          schema:
            type: string
      responses:
        "401":
          $ref: "#/components/responses/Unauthorized"
        "501":
          description: Not supported by this service
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResponseError"
    put:
      tags:
        - Presences
      operationId: sendPresence
      summary: Send a typing or recording indicator
      parameters:
        - name: EntryID
          in: path
          required: true
          description: Chat or contact id the indicator is shown in.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - presence
              properties:
                presence:
                  type: string
                  enum:
                    - typing
                    - recording
                    - paused
                delay:
                  type: integer
                  description: Seconds to keep the indicator up.
                  minimum: 0
                  maximum: 60
      responses:
        "200":
          $ref: "#/components/responses/Success"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: token
      description: "`Authorization: Bearer wsk_sg_…`"
    tokenAuth:
      type: apiKey
      in: query
      name: token
      description: "`?token=wsk_sg_…` — for clients that cannot set headers."
  parameters:
    ChatID:
      name: ChatID
      in: path
      required: true
      description: Chat id, e.g. `61371989950@s.whatsapp.net` or `1234567890@g.us`.
      schema:
        type: string
    ContactID:
      name: ContactID
      in: path
      required: true
      schema:
        type: string
    GroupID:
      name: GroupID
      in: path
      required: true
      schema:
        type: string
    MessageID:
      name: MessageID
      in: path
      required: true
      schema:
        type: string
    MediaID:
      name: MediaID
      in: path
      required: true
      schema:
        type: string
    Count:
      name: count
      in: query
      description: How many records to return.
      schema:
        type: integer
        default: 100
        minimum: 1
        maximum: 500
    Offset:
      name: offset
      in: query
      description: How many records to skip.
      schema:
        type: integer
        default: 0
        minimum: 0
  schemas:
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
          example: 429
        message:
          type: string
          example: Rate limit exceeded
        details:
          type: string
        href:
          type: string
          description: Link to the documentation for this error.
        support:
          type: string
          example: support@wasender.dev
    ResponseError:
      type: object
      required:
        - error
      properties:
        error:
          $ref: "#/components/schemas/Error"
    Health:
      type: object
      properties:
        status:
          type: object
          properties:
            text:
              type: string
              enum:
                - pending
                - connecting
                - connected
                - disconnected
                - hibernated
                - suspended
                - logged_out
            code:
              type: integer
        user:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        region:
          type: string
          enum:
            - sg
            - us-east
            - us-west
            - eu
          description: Where this channel's session runs.
        version:
          type: string
    Settings:
      type: object
      properties:
        webhooks:
          type: array
          items:
            type: object
            required:
              - url
            properties:
              url:
                type: string
                format: uri
              events:
                type: array
                items:
                  type: object
                  properties:
                    type:
                      type: string
                      enum:
                        - messages
                        - statuses
                        - chats
                        - contacts
                        - groups
                        - calls
                        - users
                    method:
                      type: string
                      enum:
                        - post
                        - put
                        - patch
                        - delete
              mode:
                type: string
                enum:
                  - body
                  - method
                default: body
        callback_persist:
          type: boolean
          default: true
        media:
          type: object
          properties:
            auto_download:
              type: array
              items:
                type: string
                enum:
                  - image
                  - video
                  - audio
                  - voice
                  - document
                  - sticker
        offline_mode:
          type: boolean
        full_history:
          type: boolean
        proxy:
          type: string
          description: Per-channel outbound proxy, e.g. `http://user:pass@host:port`.
    Profile:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        pushname:
          type: string
        status:
          type: string
          description: The "about" line
        icon:
          type: string
          format: uri
        icon_full:
          type: string
          format: uri
    MessageType:
      type: string
      enum:
        - text
        - image
        - video
        - gif
        - audio
        - voice
        - document
        - sticker
        - link_preview
        - location
        - live_location
        - contact
        - contact_list
        - poll
        - interactive
        - system
        - order
        - action
    StatusEnum:
      type: string
      enum:
        - pending
        - sent
        - delivered
        - read
        - played
        - failed
    Sender:
      type: object
      required:
        - to
      properties:
        to:
          type: string
          description: >
            Recipient. A phone number in international format without `+`, or a
            full chat id.
          pattern: ^[\d-]{9,31}(@[\w\.]{1,})?$
          example: "61371989950"
        quoted:
          type: string
          description: Id of the message this one replies to.
        edit:
          type: string
          description: Id of a message to edit instead of sending a new one.
        mentions:
          type: array
          items:
            type: string
          description: Numbers to @-mention. They must appear in the body as `@number`.
        view_once:
          type: boolean
          description: Deliver as view-once (media only).
    SenderText:
      allOf:
        - $ref: "#/components/schemas/Sender"
        - type: object
          required:
            - body
          properties:
            body:
              type: string
              description: Message text
            typing_time:
              type: integer
              description: >
                Seconds to show a typing indicator before sending. A second or
                two on

                conversational replies reads as far more human and is worth the
                latency.
              default: 0
              minimum: 0
              maximum: 60
            no_link_preview:
              type: boolean
              description: Suppress the link preview WhatsApp would otherwise generate.
            wide_link_preview:
              type: boolean
              description: Request the full-width preview card.
    SenderMedia:
      allOf:
        - $ref: "#/components/schemas/Sender"
        - type: object
          required:
            - media
          properties:
            media:
              type: string
              description: >
                One of: a URL we fetch, a base64 data URI, or the `id` of a file
                previously

                uploaded to `POST /media`.
            caption:
              type: string
            mime_type:
              type: string
    SenderMediaUpload:
      type: object
      required:
        - to
        - media
      properties:
        to:
          type: string
        media:
          type: string
          format: binary
        caption:
          type: string
        quoted:
          type: string
    Message:
      type: object
      required:
        - id
        - type
        - chat_id
        - from_me
        - timestamp
      properties:
        id:
          type: string
        type:
          $ref: "#/components/schemas/MessageType"
        subtype:
          type: string
        chat_id:
          type: string
        chat_name:
          type: string
        from:
          type: string
          description: WhatsApp id of the sender
        phone:
          type: string
        from_me:
          type: boolean
        from_name:
          type: string
          description: Sender's WhatsApp display name
        source:
          type: string
          enum:
            - api
            - web
            - mobile
            - desktop
        timestamp:
          type: integer
          description: Unix seconds
        status:
          $ref: "#/components/schemas/StatusEnum"
        text:
          type: object
          properties:
            body:
              type: string
        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:
          type: object
          properties:
            latitude:
              type: number
            longitude:
              type: number
            name:
              type: string
            address:
              type: string
        context:
          type: object
          description: Present when the message quotes another.
          properties:
            quoted_id:
              type: string
            quoted_author:
              type: string
            mentions:
              type: array
              items:
                type: string
        reactions:
          type: array
          items:
            type: object
            properties:
              emoji:
                type: string
              from:
                type: string
              timestamp:
                type: integer
        starred:
          type: boolean
    MessageMedia:
      type: object
      properties:
        id:
          type: string
        mime_type:
          type: string
        file_size:
          type: integer
        file_name:
          type: string
        sha256:
          type: string
        link:
          type: string
          format: uri
          description: Short-lived signed download URL.
        caption:
          type: string
        width:
          type: integer
        height:
          type: integer
        seconds:
          type: integer
        preview:
          type: string
          description: Base64 thumbnail.
    SentMessage:
      type: object
      required:
        - sent
      properties:
        sent:
          type: boolean
          example: true
        message:
          $ref: "#/components/schemas/Message"
    MessagesList:
      type: object
      properties:
        messages:
          type: array
          items:
            $ref: "#/components/schemas/Message"
        count:
          type: integer
        total:
          type: integer
        offset:
          type: integer
    Status:
      type: object
      required:
        - id
        - code
        - status
        - timestamp
      properties:
        id:
          type: string
        code:
          type: integer
        status:
          $ref: "#/components/schemas/StatusEnum"
        recipient_id:
          type: string
        timestamp:
          type: integer
        errors:
          type: array
          items:
            $ref: "#/components/schemas/Error"
    Chat:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
          enum:
            - contact
            - group
            - newsletter
            - broadcast
        timestamp:
          type: integer
        unread:
          type: integer
        archived:
          type: boolean
        pinned:
          type: boolean
        mute_until:
          type: integer
        labels:
          type: array
          items:
            type: string
        last_message:
          $ref: "#/components/schemas/Message"
    Contact:
      type: object
      properties:
        id:
          type: string
        lid:
          type: string
          description: WhatsApp's linked-device identifier.
        name:
          type: string
        pushname:
          type: string
        phone:
          type: string
        is_business:
          type: boolean
        is_blocked:
          type: boolean
        profile_pic:
          type: string
          format: uri
    Group:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        owner:
          type: string
        created_at:
          type: integer
        participants:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              rank:
                type: string
                enum:
                  - member
                  - admin
                  - creator
        invite_code:
          type: string
        size:
          type: integer
    MediaFile:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        mime_type:
          type: string
        size:
          type: integer
        sha256:
          type: string
        link:
          type: string
          format: uri
        created_at:
          type: integer
        expires_at:
          type: integer
  responses:
    Success:
      description: Success
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                example: true
    SentMessage:
      description: Message accepted and handed to WhatsApp
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/SentMessage"
    BadRequest:
      description: Malformed request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    Unauthorized:
      description: Missing, unknown or revoked channel token
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    PaymentRequired:
      description: >
        Free-tier limit reached, or the subscription is past due. Channels are
        blocked but

        their credentials are retained through a grace period.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    Forbidden:
      description: Not permitted — often a group action requiring admin rights
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    NotFound:
      description: No such resource on this channel
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    PayloadTooLarge:
      description: File exceeds the size limit
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    TooManyRequests:
      description: >
        Rate limit exceeded. Back off and retry after `Retry-After`. These
        limits exist to

        keep your number from being restricted by WhatsApp, so treat them as
        protection

        rather than an obstacle.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
    ChannelUnavailable:
      description: >
        The session is not currently connected — reconnecting, waking from
        hibernation, or

        its shard is restarting. Retry with backoff; this is normally transient.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ResponseError"
