openapi: 3.0.3
info:
  title: ZapClaw API
  version: 1.2.0
  description: >
    Public REST API for the ZapClaw WhatsApp gateway. Send WhatsApp text,
    template, media, sticker, location, contacts, reaction and interactive
    messages, show typing indicators, mark messages as read, quote messages
    with replyTo, upload, download and delete media, manage message templates,
    read and update the business profile, block users, manage QR codes,
    configure conversational automation, and read message, number and pricing
    analytics data. Inbound messages, delivery status updates and WABA account
    events are delivered to a webhook URL configured in the ZapClaw dashboard.
    See https://zapclaw.app/docs.html for the webhook payload format.


    Coexistence limitations: numbers run in Meta Coexistence (Coex) mode.
    Throughput is fixed at about 5 messages per second regardless of messaging
    tier. Messages a business sends from WhatsApp for Windows or WearOS do not
    generate webhooks (an unavoidable data gap). Voice/video calls, the
    Official Business Account green badge (OBA), and ephemeral, view-once and
    group messages are not supported.
  contact:
    name: ZapClaw
    email: contato@zapclaw.app
    url: https://zapclaw.app/docs.html

servers:
  - url: https://api.zapclaw.app
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - name: Messages
    description: Send and read messages
  - name: Numbers
    description: Discover connected WhatsApp numbers
  - name: Media
    description: Upload, download and delete media files
  - name: Templates
    description: Read and manage message templates
  - name: Profile
    description: Read and update the WhatsApp business profile
  - name: Block
    description: Block, unblock and list blocked users
  - name: QR Codes
    description: Manage QR codes and managed links
  - name: Automation
    description: Configure conversational automation
  - name: Analytics
    description: Read pricing analytics

paths:
  /api/v1/send/text:
    post:
      tags: [Messages]
      summary: Send a text message
      operationId: sendText
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, text]
              properties:
                from:
                  type: string
                  description: Sender number — a connected WhatsApp number, E.164 digits.
                  example: "5511999999999"
                to:
                  type: string
                  description: Recipient number, E.164 digits.
                  example: "5511888888888"
                text:
                  type: string
                  maxLength: 4096
                  example: Hello from ZapClaw
                previewUrl:
                  type: boolean
                  description: >
                    When true, WhatsApp renders a link preview for the first
                    URL found in `text`.
                  default: false
                replyTo:
                  type: string
                  description: >
                    Optional wamid of a message to quote. The message is
                    delivered as a quoted reply in WhatsApp.
                  example: "wamid.HBg..."
      responses:
        "201":
          description: Message accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/template:
    post:
      tags: [Messages]
      summary: Send a template message
      operationId: sendTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, templateName]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                to:
                  type: string
                  example: "5511888888888"
                templateName:
                  type: string
                  description: Name of an approved template.
                  example: order_update
                language:
                  type: string
                  description: Template language code.
                  default: pt_BR
                components:
                  type: array
                  description: Template components to fill variables (Meta format).
                  items:
                    type: object
                replyTo:
                  type: string
                  description: >
                    Optional wamid of a message to quote. The message is
                    delivered as a quoted reply in WhatsApp.
                  example: "wamid.HBg..."
      responses:
        "201":
          description: Message accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/media:
    post:
      tags: [Messages]
      summary: Send a media message
      operationId: sendMedia
      description: >
        Sends an image, video, document, audio or sticker message. The media is
        referenced either by a public https URL (`link`) or by a `mediaId`
        handle obtained from POST /api/v1/media. Provide exactly one of the two.
        A sticker (`type: sticker`) must be a WebP image and does not accept a
        caption.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, type]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                to:
                  type: string
                  example: "5511888888888"
                type:
                  type: string
                  enum: [image, video, document, audio, sticker]
                link:
                  type: string
                  format: uri
                  description: >
                    Public https URL of the media file. Provide either `link`
                    or `mediaId`, never both.
                  example: https://example.com/photo.jpg
                mediaId:
                  type: string
                  description: >
                    Media handle returned by POST /api/v1/media. Provide either
                    `link` or `mediaId`, never both.
                  example: "1029384756102938"
                caption:
                  type: string
                  description: Caption (image, video, document).
                filename:
                  type: string
                  description: File name (document only).
                replyTo:
                  type: string
                  description: >
                    Optional wamid of a message to quote. The message is
                    delivered as a quoted reply in WhatsApp.
                  example: "wamid.HBg..."
      responses:
        "201":
          description: Message accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/location:
    post:
      tags: [Messages]
      summary: Send a location
      operationId: sendLocation
      description: Sends a static location pin.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, latitude, longitude]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                to:
                  type: string
                  example: "5511888888888"
                latitude:
                  type: number
                  format: double
                  minimum: -90
                  maximum: 90
                  example: -23.561414
                longitude:
                  type: number
                  format: double
                  minimum: -180
                  maximum: 180
                  example: -46.655881
                name:
                  type: string
                  description: Name of the location.
                  example: Avenida Paulista
                address:
                  type: string
                  description: Street address of the location.
                  example: "Av. Paulista, 1578 - São Paulo, SP"
                replyTo:
                  type: string
                  description: >
                    Optional wamid of a message to quote. The message is
                    delivered as a quoted reply in WhatsApp.
                  example: "wamid.HBg..."
      responses:
        "201":
          description: Message accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/contacts:
    post:
      tags: [Messages]
      summary: Send contacts
      operationId: sendContacts
      description: >
        Sends one or more contact cards. The `contacts` array follows the
        WhatsApp Cloud API contacts object shape and is passed through to Meta
        unchanged.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, contacts]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                to:
                  type: string
                  example: "5511888888888"
                contacts:
                  type: array
                  minItems: 1
                  description: >
                    A non-empty array of WhatsApp Cloud API contact objects.
                  items:
                    type: object
                  example:
                    - name:
                        formatted_name: "Maria Silva"
                        first_name: "Maria"
                        last_name: "Silva"
                      phones:
                        - phone: "+5511777777777"
                          type: WORK
                          wa_id: "5511777777777"
                replyTo:
                  type: string
                  description: >
                    Optional wamid of a message to quote. The message is
                    delivered as a quoted reply in WhatsApp.
                  example: "wamid.HBg..."
      responses:
        "201":
          description: Message accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/reaction:
    post:
      tags: [Messages]
      summary: React to a message
      operationId: sendReaction
      description: >
        Reacts to a message with an emoji. An empty or omitted `emoji` removes
        a reaction previously sent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, messageId]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                to:
                  type: string
                  example: "5511888888888"
                messageId:
                  type: string
                  description: The wamid of the message being reacted to.
                  example: "wamid.HBg..."
                emoji:
                  type: string
                  description: >
                    The reaction emoji. An empty or omitted value removes a
                    previous reaction.
                  example: "👍"
      responses:
        "201":
          description: Reaction accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/interactive:
    post:
      tags: [Messages]
      summary: Send an interactive message
      operationId: sendInteractive
      description: >
        Sends an interactive message: reply buttons, a list, or a
        call-to-action URL. The `interactive` object follows the WhatsApp Cloud
        API interactive object shape and is passed through to Meta unchanged.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, interactive]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                to:
                  type: string
                  example: "5511888888888"
                interactive:
                  type: object
                  description: >
                    The WhatsApp Cloud API interactive object (type `button`,
                    `list` or `cta_url`). Passed through to Meta unchanged.
                  example:
                    type: button
                    body: { text: "Did your order arrive?" }
                    action:
                      buttons:
                        - type: reply
                          reply: { id: "yes", title: "Yes" }
                        - type: reply
                          reply: { id: "no", title: "No" }
                replyTo:
                  type: string
                  description: >
                    Optional wamid of a message to quote. The message is
                    delivered as a quoted reply in WhatsApp.
                  example: "wamid.HBg..."
      responses:
        "201":
          description: Interactive message accepted and sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/typing:
    post:
      tags: [Messages]
      summary: Show a typing indicator
      operationId: sendTyping
      description: >
        Shows a typing indicator to the contact and marks the referenced
        inbound message as read. The indicator clears after about 25 seconds,
        or as soon as the next message is sent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, messageId]
              properties:
                from:
                  type: string
                  description: The connected number that received the message (E.164).
                  example: "5511999999999"
                messageId:
                  type: string
                  description: The wamid of the inbound message to respond to.
                  example: "wamid.HBg..."
      responses:
        "200":
          description: Typing indicator shown.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: typing
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/read:
    post:
      tags: [Messages]
      summary: Mark an inbound message as read
      operationId: markRead
      description: >
        Sends a read receipt for an inbound message, marking it as read in the
        sender's WhatsApp app.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, messageId]
              properties:
                from:
                  type: string
                  description: The connected number that received the message (E.164).
                  example: "5511999999999"
                messageId:
                  type: string
                  description: The inbound message wamid (Meta message id) to mark as read.
                  example: "wamid.HBg..."
      responses:
        "200":
          description: Read receipt sent.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: read
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/numbers:
    get:
      tags: [Numbers]
      summary: List connected WhatsApp numbers
      operationId: listNumbers
      description: >
        Lists the caller's connected WhatsApp numbers. Use this to discover
        valid `from` values for the send endpoints.
      responses:
        "200":
          description: Connected numbers.
          content:
            application/json:
              schema:
                type: object
                properties:
                  numbers:
                    type: array
                    items: { $ref: "#/components/schemas/Number" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/v1/send/messages:
    get:
      tags: [Messages]
      summary: List recent messages
      operationId: listMessages
      parameters:
        - name: from
          in: query
          required: false
          schema: { type: string }
          description: Filter by sender number (E.164).
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        "200":
          description: Recent messages.
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items: { $ref: "#/components/schemas/Message" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/v1/send/messages/{id}:
    get:
      tags: [Messages]
      summary: Get a single message
      operationId: getMessage
      description: Looks up a single message by its ZapClaw id.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
          description: ZapClaw message id.
      responses:
        "200":
          description: The message.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /api/v1/media:
    post:
      tags: [Media]
      summary: Upload a media file
      operationId: uploadMedia
      description: >
        Uploads a file and returns a `media_id` handle that can be sent with
        POST /api/v1/send/media. Per-type size caps: image 5 MB, video 16 MB,
        audio 16 MB, document 100 MB.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, from]
              properties:
                file:
                  type: string
                  format: binary
                  description: The media file to upload.
                from:
                  type: string
                  description: A connected WhatsApp number, E.164 digits.
                  example: "5511999999999"
      responses:
        "201":
          description: File uploaded.
          content:
            application/json:
              schema:
                type: object
                properties:
                  media_id:
                    type: string
                    example: "1029384756102938"
                  mime_type:
                    type: string
                    example: image/jpeg
                  file_size:
                    type: integer
                    description: Size of the uploaded file in bytes.
                    example: 12345
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/media/{mediaId}:
    get:
      tags: [Media]
      summary: Download received media
      operationId: downloadMedia
      description: >
        Downloads the bytes of media received on an inbound WhatsApp message.
        The response is the raw file streamed with the correct `Content-Type`.
        Use the `media_url` provided in the `message.received` webhook payload,
        which points at this endpoint.
      parameters:
        - name: mediaId
          in: path
          required: true
          schema: { type: string }
          description: The media id of a received media message.
      responses:
        "200":
          description: The media file.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404":
          description: The media id is unknown to the caller's account.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "410":
          description: The media has aged out on Meta's side (~30 days).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
    delete:
      tags: [Media]
      summary: Delete uploaded media
      operationId: deleteMedia
      description: >
        Deletes a media file uploaded to Meta with POST /api/v1/media. Uploaded
        media also expires on Meta's side automatically after about 30 days.
      parameters:
        - name: mediaId
          in: path
          required: true
          schema: { type: string }
          description: The media id returned by POST /api/v1/media.
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: Media deleted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Deleted" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/templates:
    get:
      tags: [Templates]
      summary: List approved templates
      operationId: listTemplates
      parameters:
        - name: from
          in: query
          required: false
          schema: { type: string }
          description: Select which connected number's templates to return (E.164).
      responses:
        "200":
          description: Approved templates available to send.
          content:
            application/json:
              schema:
                type: object
                properties:
                  templates:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Meta template id.
                          example: "1234567890123456"
                        name: { type: string }
                        language: { type: string }
                        category: { type: string }
                        status: { type: string }
                        bodyText: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Templates]
      summary: Create a message template
      operationId: createTemplate
      description: >
        Creates a message template and submits it to Meta for review. A newly
        created template is not sendable until Meta approves it.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, name, category, language, components]
              properties:
                from:
                  type: string
                  description: A connected WhatsApp number, E.164 digits.
                  example: "5511999999999"
                name:
                  type: string
                  description: Template name (lowercase, digits and underscores).
                  example: order_shipped
                category:
                  type: string
                  enum: [UTILITY, MARKETING, AUTHENTICATION]
                language:
                  type: string
                  description: Template language code.
                  example: pt_BR
                components:
                  type: array
                  minItems: 1
                  description: Template components (Meta format).
                  items:
                    type: object
      responses:
        "201":
          description: Template created and submitted for Meta review.
          content:
            application/json:
              schema:
                type: object
                properties:
                  template:
                    type: object
                    properties:
                      id: { type: string, example: "1234567890123456" }
                      status: { type: string, example: PENDING }
                      category: { type: string, example: UTILITY }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/send/templates/{id}:
    post:
      tags: [Templates]
      summary: Edit a message template
      operationId: editTemplate
      description: >
        Edits an existing template, identified by its Meta template id (the
        `id` field returned by GET /api/v1/send/templates). Provide at least
        one of `category` or `components`. An edited template is re-submitted
        to Meta for review.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
          description: The Meta template id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                category:
                  type: string
                  description: New template category.
                  enum: [UTILITY, MARKETING, AUTHENTICATION]
                components:
                  type: array
                  description: New template components (Meta format).
                  items:
                    type: object
      responses:
        "200":
          description: Template updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Updated" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/BadGateway" }
    delete:
      tags: [Templates]
      summary: Delete a message template
      operationId: deleteTemplate
      description: Deletes a template by its name.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
          description: >
            The template name. The path segment is the template name, not the
            Meta template id.
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: Template deleted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Deleted" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/profile:
    get:
      tags: [Profile]
      summary: Read the business profile
      operationId: getProfile
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: The business profile.
          content:
            application/json:
              schema:
                type: object
                properties:
                  profile: { $ref: "#/components/schemas/BusinessProfile" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }
    patch:
      tags: [Profile]
      summary: Update the business profile
      operationId: updateProfile
      description: >
        Updates the business profile text fields. Provide at least one field
        beyond `from`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                about:
                  type: string
                  example: Open Monday to Friday, 9am to 6pm
                address:
                  type: string
                description:
                  type: string
                email:
                  type: string
                websites:
                  type: array
                  items: { type: string, format: uri }
                vertical:
                  type: string
                  description: Business industry vertical (Meta enum).
      responses:
        "200":
          description: Profile updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Updated" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/profile/name:
    post:
      tags: [Profile]
      summary: Submit a display name
      operationId: updateName
      description: >
        Submits a new display name for a connected number. The change is
        reviewed by Meta before it takes effect.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, name]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                name:
                  type: string
                  example: My Business
      responses:
        "200":
          description: Display name change submitted for Meta review.
          content:
            application/json:
              schema:
                type: object
                properties:
                  submitted: { type: boolean, example: true }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/profile/photo:
    post:
      tags: [Profile]
      summary: Set the profile picture
      operationId: updatePhoto
      description: >
        Sets the business profile picture. The image file is capped at 5 MB.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, from]
              properties:
                file:
                  type: string
                  format: binary
                  description: The profile picture image.
                from:
                  type: string
                  description: A connected WhatsApp number, E.164 digits.
                  example: "5511999999999"
      responses:
        "200":
          description: Profile picture updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Updated" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/block:
    get:
      tags: [Block]
      summary: List blocked users
      operationId: listBlocked
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: Blocked users.
          content:
            application/json:
              schema:
                type: object
                properties:
                  blocked:
                    type: array
                    items:
                      type: object
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }
    post:
      tags: [Block]
      summary: Block users
      operationId: blockUsers
      description: >
        Blocks one or more users on a connected number. Meta only allows
        blocking a user who has messaged the business within the last 24 hours.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BlockRequest" }
      responses:
        "200":
          description: Block result.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BlockResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }
    delete:
      tags: [Block]
      summary: Unblock users
      operationId: unblockUsers
      description: Unblocks one or more previously blocked users on a connected number.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BlockRequest" }
      responses:
        "200":
          description: Unblock result.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BlockResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/qr-codes:
    get:
      tags: [QR Codes]
      summary: List QR codes
      operationId: listQrCodes
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: QR codes.
          content:
            application/json:
              schema:
                type: object
                properties:
                  qrCodes:
                    type: array
                    items: { $ref: "#/components/schemas/QrCode" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }
    post:
      tags: [QR Codes]
      summary: Create a QR code
      operationId: createQrCode
      description: Creates a QR code (managed link) for a connected number.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, prefilledMessage]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                prefilledMessage:
                  type: string
                  description: The message text prefilled when a user opens the link.
                  example: Hi, I would like to know more
                imageFormat:
                  type: string
                  enum: [PNG, SVG]
                  default: PNG
      responses:
        "201":
          description: QR code created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  qrCode: { $ref: "#/components/schemas/QrCode" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/qr-codes/{code}:
    delete:
      tags: [QR Codes]
      summary: Delete a QR code
      operationId: deleteQrCode
      parameters:
        - name: code
          in: path
          required: true
          schema: { type: string }
          description: The QR code identifier.
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: QR code deleted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Deleted" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/automation:
    get:
      tags: [Automation]
      summary: Read conversational automation
      operationId: getAutomation
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
      responses:
        "200":
          description: Conversational automation config.
          content:
            application/json:
              schema:
                type: object
                properties:
                  automation: { $ref: "#/components/schemas/Automation" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }
    post:
      tags: [Automation]
      summary: Update conversational automation
      operationId: updateAutomation
      description: >
        Updates the conversational automation config. Provide at least one
        field beyond `from`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from]
              properties:
                from:
                  type: string
                  example: "5511999999999"
                enableWelcomeMessage:
                  type: boolean
                  description: >
                    Whether the welcome message is shown when a customer opens
                    a new conversation.
                commands:
                  type: array
                  description: Slash commands.
                  items:
                    type: object
                    properties:
                      command_name: { type: string }
                      command_description: { type: string }
                prompts:
                  type: array
                  maxItems: 4
                  description: Ice breaker prompts (maximum 4).
                  items: { type: string }
      responses:
        "200":
          description: Automation config updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Updated" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

  /api/v1/analytics/pricing:
    get:
      tags: [Analytics]
      summary: Pricing analytics
      operationId: getPricingAnalytics
      description: >
        Returns Meta's per-message pricing analytics for a time window. Use it
        alongside the `pricing` and `conversation` objects on the
        message.status webhook to reconcile billing.
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string }
          description: A connected WhatsApp number (E.164).
        - name: start
          in: query
          required: true
          schema: { type: integer }
          description: Start of the window, a Unix timestamp (epoch seconds).
        - name: end
          in: query
          required: true
          schema: { type: integer }
          description: End of the window, a Unix timestamp (epoch seconds).
        - name: granularity
          in: query
          required: false
          schema:
            type: string
            enum: [DAILY, MONTHLY]
            default: DAILY
      responses:
        "200":
          description: Pricing analytics.
          content:
            application/json:
              schema:
                type: object
                properties:
                  analytics:
                    type: object
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/BadGateway" }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >
        API key created in the ZapClaw dashboard. Format: zc_live_ followed
        by 32 random characters. Shown only once, on creation.

  schemas:
    Message:
      type: object
      properties:
        id: { type: string, format: uuid }
        direction: { type: string, enum: [inbound, outbound] }
        to: { type: string, description: Recipient number }
        type: { type: string, example: text }
        templateName: { type: string, nullable: true }
        content: { type: string, nullable: true }
        wamid: { type: string, nullable: true, description: Meta message id }
        status:
          type: string
          enum: [pending, sent, delivered, read, failed, received]
        errorMessage: { type: string, nullable: true }
        sentAt: { type: string, format: date-time, nullable: true }
        createdAt: { type: string, format: date-time }

    MessageResponse:
      type: object
      properties:
        message: { $ref: "#/components/schemas/Message" }

    Number:
      type: object
      properties:
        phoneNumberId: { type: string, description: Meta phone number id }
        displayPhoneNumber: { type: string, description: The number in display format }
        displayName: { type: string, description: Verified business display name }
        label: { type: string, nullable: true, description: Custom label set in the dashboard }
        qualityRating: { type: string, description: "Meta quality rating: GREEN, YELLOW or RED" }
        status: { type: string, example: active }

    Updated:
      type: object
      description: Confirms a resource was updated.
      properties:
        updated:
          type: boolean
          example: true

    Deleted:
      type: object
      description: Confirms a resource was deleted.
      properties:
        deleted:
          type: boolean
          example: true

    BusinessProfile:
      type: object
      properties:
        about: { type: string }
        address: { type: string }
        description: { type: string }
        email: { type: string }
        websites:
          type: array
          items: { type: string, format: uri }
        vertical: { type: string, description: Business industry vertical (Meta enum) }
        profile_picture_url: { type: string, format: uri }

    BlockRequest:
      type: object
      required: [from, numbers]
      properties:
        from:
          type: string
          description: A connected WhatsApp number, E.164 digits.
          example: "5511999999999"
        numbers:
          type: array
          minItems: 1
          description: Recipient numbers (E.164) to block or unblock.
          items: { type: string }
          example: ["5511777777777"]

    BlockResult:
      type: object
      description: >
        The raw Meta block/unblock result, passed through unchanged.
      properties:
        result:
          type: object

    QrCode:
      type: object
      properties:
        code:
          type: string
          description: The QR code identifier.
          example: "4O4ABCDEFG123"
        prefilled_message:
          type: string
          example: Hi, I would like to know more
        deep_link_url:
          type: string
          format: uri
          description: The wa.me managed link.
          example: "https://wa.me/message/4O4ABCDEFG123"
        qr_image_url:
          type: string
          format: uri
          description: URL of the rendered QR image.

    Automation:
      type: object
      properties:
        enable_welcome_message:
          type: boolean
        commands:
          type: array
          items:
            type: object
            properties:
              command_name: { type: string }
              command_description: { type: string }
        prompts:
          type: array
          description: Ice breaker prompts (maximum 4).
          items: { type: string }

    Error:
      type: object
      description: >
        Standard error body returned with every non-2xx response.
      properties:
        error:
          type: string
          description: Human-readable error message.
      example:
        error: "from, to and text are required"

  responses:
    BadRequest:
      description: Invalid request, or unknown "from" number.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: The requested resource was not found.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    PayloadTooLarge:
      description: The uploaded file exceeds the size cap for its type.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: Rate limit exceeded (1000 requests / 15 minutes per key).
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    BadGateway:
      description: The Meta API returned a failure or could not be reached.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
