openapi: 3.0.3
info:
  title: WhatsApp Multi-Instance API
  description: |
    RESTful API for managing multiple WhatsApp instances.

    **Base URL:** use the host you opened this spec on (each Azure region is its own base URL, e.g. `https://wasup-uk-south.azurewebsites.net`). The Scalar page rewrites `servers` automatically.

    ## Authentication
    When `API_KEY` is set on the server, **every** `/api/...` request must include that key (admin dashboard, external integrations, and tools).

    Use one of:
    - Header: `X-API-Key: <your-api-key-for-this-deployment>`
    - Header: `Authorization: Bearer <your-api-key-for-this-deployment>`

    Example (replace `BASE` and `KEY` with this region’s URL and key):

    ```bash
    curl -sS -H "X-API-Key: KEY" "BASE/api/instances"
    ```

    ```bash
    curl -sS -X POST "BASE/api/instances/INSTANCE_ID/send" \
      -H "X-API-Key: KEY" -H "Content-Type: application/json" \
      -d '{"to":"447700900000@s.whatsapp.net","message":"Hello"}'
    ```

    To reveal this deployment’s key from the browser, open **`/docs`** and use the password gate (see `DOCS_REVEAL_PASSWORD` / default in server env).

    ## WebSocket
    Connect to `wss://<same-host>/ws` (or `ws://` locally). If `API_KEY` is set, send `{"type":"auth","apiKey":"<key>"}` after connect and wait for `auth_success` before relying on live events.

    Events: `init`, `auth_success`, `auth_failed`, `instance_created`, `instance_status`, `instance_updated`, `instance_deleted`, `message`, `log`
  version: 1.0.0
  contact:
    name: API Support
  license:
    name: MIT

servers:
  - url: https://3djaqowrnor4fayvwdyqx35yed0-1jojsgt.wasup.co
    description: This deployment
tags:
  - name: Whitelabel
    description: Single-call onboarding and customer-facing convenience endpoints
  - name: Instances
    description: Instance management (CRUD)
  - name: Connection
    description: WhatsApp connection control
  - name: Messaging
    description: |
      Send messages, media, interactive buttons, lists, reactions, and more.
      Buttons and lists use Wasup's interactive helper layer so native controls render on iOS and Android.
  - name: Reactions
    description: Send emoji reactions to messages
  - name: Handoff
    description: Human handoff — pause bot responses per-chat when a human takes over
  - name: Profile
    description: WhatsApp profile management (name, picture, status)
  - name: Webhook
    description: Inbound webhook configuration and testing
  - name: Behavior
    description: Typing simulation and delay settings
  - name: Anti-Ban
    description: Rate limiting and anti-ban settings (legacy)
  - name: Wasup Anti-Ban
    description: |
      Wasup multi-module anti-ban pipeline: WarmUp, RateLimiter (Gaussian jitter), HealthMonitor,
      TimelockGuard, ReplyRatioGuard, ContactGraphWarmer, PresenceChoreographer (WPM + circadian),
      RetryReasonTracker, PostReconnectThrottle, LidResolver/JidCanonicalizer,
      SessionHealthMonitor, disconnect-driven backoff, and stealth connect
      (random browser fingerprint + delayed presence ramp). State is persisted per-instance
      under `instances/<id>/antiban/`.

      All endpoints are **per-instance**. Use the global Battlespace dashboard to control
      instances across regions.
  - name: Logs
    description: Activity logs
  - name: System
    description: Health and status

security:
  - ApiKeyAuth: []
  - BearerAuth: []

paths:
  # ==========================================
  # WHITELABEL (single-call onboarding)
  # ==========================================
  /api/onboard:
    post:
      tags: [Whitelabel]
      summary: Onboard a new WhatsApp number
      description: |
        Single-call endpoint that creates an instance, configures its webhook,
        and connects via pairing code — all in one request.

        If the phone number already has an active instance, returns the existing
        instance instead of creating a duplicate.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone]
              properties:
                phone:
                  type: string
                  description: Phone number with country code (+ prefix optional)
                  example: '447393002183'
                name:
                  type: string
                  description: Friendly name for the instance
                  example: Acme Corp
                webhookUrl:
                  type: string
                  format: uri
                  description: URL to forward inbound messages to
                  example: https://acme.com/webhook
                profileName:
                  type: string
                  description: WhatsApp display name (applied once connected)
                  example: Acme Support
                profileStatus:
                  type: string
                  description: WhatsApp "About" text (applied once connected)
                  example: We reply within minutes
                behaviorSettings:
                  $ref: '#/components/schemas/BehaviorSettings'
                  description: |
                    Optional behavior overrides at onboarding time. For
                    human-monitored deployments (clinics, support desks), use
                    `behaviorProfile: notification-balanced` so inbound
                    webhooks stay immediate while handset alerts are prioritized.
            examples:
              minimal:
                summary: Phone only
                value:
                  phone: '447393002183'
              full:
                summary: Full onboarding
                value:
                  phone: '447393002183'
                  name: Acme Corp
                  webhookUrl: https://acme.com/webhook
                  profileName: Acme Support
                  profileStatus: We reply within minutes
              clinic:
                summary: Clinic / human-monitored (phone notifications on)
                value:
                  phone: '447393002183'
                  name: Smile Dental
                  webhookUrl: https://wasup.co/webhook/...
                  behaviorSettings:
                    behaviorProfile: notification-balanced
                    notificationGraceMs: 12000
                    typingSimulation: true
      responses:
        '201':
          description: Instance created and pairing code generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardResponse'
              example:
                success: true
                instanceId: wa_abc123
                pairingCode: 'A1B2-C3D4'
                status: connecting
                message: 'Enter code A1B2-C3D4 in WhatsApp > Linked Devices > Link a Device'
        '200':
          description: Instance already exists for this phone
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardResponse'
              example:
                success: true
                instanceId: wa_abc123
                pairingCode: null
                status: connected
                message: Instance already exists for this phone number
        '400':
          $ref: '#/components/responses/BadRequest'

  # ==========================================
  # INSTANCE MANAGEMENT
  # ==========================================
  /api/instances:
    get:
      tags: [Instances]
      summary: List all instances
      description: Get a list of all WhatsApp instances with their current status
      responses:
        '200':
          description: List of instances
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  count:
                    type: integer
                    example: 2
                  instances:
                    type: array
                    items:
                      $ref: '#/components/schemas/Instance'
    
    post:
      tags: [Instances]
      summary: Create new instance
      description: Create a new WhatsApp instance
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: string
                  description: Custom instance ID (auto-generated if not provided)
                  example: my_custom_id
                name:
                  type: string
                  description: Display name for the instance
                  example: Customer Support
                webhookUrl:
                  type: string
                  format: uri
                  description: URL to forward incoming messages to
                  example: https://your-api.com/webhook
                behaviorSettings:
                  $ref: '#/components/schemas/BehaviorSettings'
                antiBanSettings:
                  $ref: '#/components/schemas/AntiBanSettings'
      responses:
        '201':
          description: Instance created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  instance:
                    $ref: '#/components/schemas/Instance'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    
    get:
      tags: [Instances]
      summary: Get instance details
      description: Get detailed information about a specific instance
      responses:
        '200':
          description: Instance details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  instance:
                    $ref: '#/components/schemas/Instance'
        '404':
          $ref: '#/components/responses/NotFound'
    
    put:
      tags: [Instances]
      summary: Update instance
      description: |
        Update name, webhook, anti-ban, or **behaviour** (including
        `behaviorProfile`) in one call. Behaviour can also be updated
        via `PUT /api/instances/{instanceId}/behavior`; both paths are equivalent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: Updated Name
                webhookUrl:
                  type: string
                  format: uri
                  example: https://new-webhook.com/endpoint
                behaviorSettings:
                  $ref: '#/components/schemas/BehaviorSettings'
                antiBanSettings:
                  $ref: '#/components/schemas/AntiBanSettings'
      responses:
        '200':
          description: Instance updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  instance:
                    $ref: '#/components/schemas/Instance'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
    
    delete:
      tags: [Instances]
      summary: Delete instance
      description: Delete an instance and all its data
      responses:
        '200':
          description: Instance deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Instance wa_xxx deleted
        '400':
          $ref: '#/components/responses/BadRequest'

  # ==========================================
  # CONNECTION
  # ==========================================
  /api/instances/{instanceId}/connect:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Connection]
      summary: Connect instance
      description: |
        Start WhatsApp connection. Defaults to QR mode — poll `/qr` for the code.
        Pass `pairingPhone` to use pairing code mode instead.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                pairingPhone:
                  type: string
                  description: Phone number with country code (no + prefix). If provided, uses pairing code instead of QR.
                  example: '447393002183'
      responses:
        '200':
          description: Connection started
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Connection started (QR mode)
                  pairingCode:
                    type: string
                    nullable: true
                    description: Pairing code (only when pairingPhone was provided)
                    example: 'A1B2-C3D4'
                  instance:
                    $ref: '#/components/schemas/Instance'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/disconnect:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Connection]
      summary: Disconnect instance
      description: |
        Closes the live WhatsApp socket. **Credentials on disk stay valid** so `connect` can resume without a new QR.
        Pass JSON body `{ "revoke": true }` to perform a full server-side logout (rare; use clear-auth to also delete local credential files).
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                revoke:
                  type: boolean
                  default: false
                  description: If true, revoke the session on WhatsApp servers (optional).
      responses:
        '200':
          description: Disconnected
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Disconnected
                  instance:
                    $ref: '#/components/schemas/Instance'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/clear-auth:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Connection]
      summary: Clear auth
      description: Logout and delete saved credentials. Requires new QR scan.
      responses:
        '200':
          description: Auth cleared
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Auth cleared
                  instance:
                    $ref: '#/components/schemas/Instance'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/pair:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Connection]
      summary: Connect via pairing code
      description: |
        Connect using a pairing code instead of QR scan.
        Returns a short code (e.g. `A1B2-C3D4`) for the user to enter in
        WhatsApp > Linked Devices > Link a Device.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phoneNumber]
              properties:
                phoneNumber:
                  type: string
                  description: Phone number with country code, no + prefix
                  example: '447393002183'
      responses:
        '200':
          description: Pairing code generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  pairingCode:
                    type: string
                    example: 'A1B2-C3D4'
                  message:
                    type: string
                    example: 'Enter code A1B2-C3D4 in WhatsApp > Linked Devices > Link a Device'
                  instance:
                    $ref: '#/components/schemas/Instance'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/qr:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Connection]
      summary: Get QR / pairing code
      description: |
        Returns current QR code and/or pairing code for the instance.

        Use `?format=image` to get a raw PNG image suitable for `<img>` tags or
        mobile app rendering. Returns `204 No Content` if the instance is already
        connected or no QR code is available yet.
      parameters:
        - name: format
          in: query
          schema:
            type: string
            enum: [image]
          description: Set to `image` to receive a raw PNG instead of JSON
      responses:
        '200':
          description: QR / pairing code (JSON)
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  status:
                    type: string
                    enum: [disconnected, connecting, connected]
                    example: connecting
                  qrCode:
                    type: string
                    nullable: true
                    description: Base64 data URL of QR code image
                    example: data:image/png;base64,iVBORw0KGgo...
                  pairingCode:
                    type: string
                    nullable: true
                    description: Pairing code (if using pairing code mode)
                    example: 'A1B2-C3D4'
                  phone:
                    type: string
                    nullable: true
                    description: Connected phone (when status is 'connected')
                  message:
                    type: string
                    example: Not yet generated. Call /connect or /pair first.
            image/png:
              schema:
                type: string
                format: binary
                description: Raw QR code PNG (when ?format=image)
        '204':
          description: No QR code available (already connected or not yet generated)
        '404':
          $ref: '#/components/responses/NotFound'

  /api/instances/{instanceId}/connection:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Whitelabel, Connection]
      summary: Poll connection status
      description: |
        Lightweight endpoint for polling connection state.
        Returns only connection-relevant fields — simpler than the full
        instance detail endpoint.
      responses:
        '200':
          description: Connection status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionStatus'
              example:
                success: true
                status: connected
                phone: '447393002183'
                connectedAt: '2026-01-29T12:00:00.000Z'
                uptime: 3600
                pairingCode: null
                qrCode: null
        '404':
          $ref: '#/components/responses/NotFound'

  # ==========================================
  # MESSAGING
  # ==========================================
  /api/instances/{instanceId}/send:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Messaging]
      summary: Send message via instance
      description: |
        Send a WhatsApp message through a specific instance.
        Supports text, image, video, document, audio, reply buttons, list menus, location pins, and contact cards.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageBody'
      responses:
        '200':
          description: Message sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  messageType:
                    type: string
                    example: text
                  result:
                    $ref: '#/components/schemas/SendResult'
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/send:
    post:
      tags: [Messaging]
      summary: Send message (auto-select instance)
      description: |
        Send a message using a connected phone number or auto-select the first connected instance.
        Supports all rich message types (image, video, buttons, lists, etc.).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/SendMessageBody'
                - type: object
                  properties:
                    from_phone:
                      type: string
                      description: Your connected phone number to send from (optional, auto-selects if omitted)
                      example: '60123456789'
                    to_phone:
                      type: string
                      description: Alias for `to`
                      example: '60198765432'
      responses:
        '200':
          description: Message sent
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    message_id:
                      type: string
                      format: uuid
                    created_at:
                      type: string
                      format: date-time
                    from_phone:
                      type: string
                    to_phone:
                      type: string
                    message:
                      type: string
                    message_type:
                      type: string
                      example: text
                    status:
                      type: string
                      enum: [sent, rate_limited, failed]
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/messages:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Whitelabel, Messaging]
      summary: Get message history
      description: |
        Returns recent inbound and outbound messages for the instance.
        Messages are stored in-memory (capped at 1000) and reset on server restart.
      parameters:
        - name: direction
          in: query
          schema:
            type: string
            enum: [inbound, outbound]
          description: Filter by message direction (omit for both)
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          description: Max messages to return
        - name: since
          in: query
          schema:
            type: string
            format: date-time
          description: Only return messages after this ISO timestamp
          example: '2026-01-29T00:00:00.000Z'
      responses:
        '200':
          description: Message history
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  count:
                    type: integer
                    example: 12
                  messages:
                    type: array
                    items:
                      $ref: '#/components/schemas/Message'
        '404':
          $ref: '#/components/responses/NotFound'

  # ==========================================
  # REACTIONS
  # ==========================================
  /api/instances/{instanceId}/react:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Reactions, Messaging]
      summary: React to a message (instance)
      description: |
        Send an emoji reaction to a specific message via a named instance.
        Pass an empty string for `emoji` to remove an existing reaction.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReactionBody'
      responses:
        '200':
          description: Reaction sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  emoji: { type: string, example: '🔥' }
                  messageId: { type: string }
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/react:
    post:
      tags: [Reactions, Messaging]
      summary: React to a message (auto-select instance)
      description: |
        Send an emoji reaction using `from_phone` to match a connected instance, or
        auto-selects the first connected instance when omitted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/ReactionBody'
                - type: object
                  properties:
                    from_phone:
                      type: string
                      description: Sender phone number to match an instance (optional)
                      example: '358942721757'
                  required: []
      responses:
        '200':
          description: Reaction sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  instance_id: { type: string }
                  emoji: { type: string }
                  messageId: { type: string }
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

  # ==========================================
  # HUMAN HANDOFF
  # ==========================================
  /api/instances/{instanceId}/handoff:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Handoff]
      summary: Get active human-handoff chats
      description: |
        Returns the list of chat JIDs that are currently in human-mode
        (bot responses are paused). A chat enters human-mode when you send
        a manual message from the phone or via this API. Resume bot mode by
        sending a message containing `#ai`, `#assistant`, `#bot`, or `#resume`.
      responses:
        '200':
          description: Handoff list
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  chats:
                    type: array
                    items:
                      type: object
                      properties:
                        jid: { type: string, example: '447835156367@s.whatsapp.net' }
                        since: { type: string, format: date-time }
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      tags: [Handoff]
      summary: Tag or untag a chat for human handoff
      description: |
        Manually pause or resume bot responses for a specific phone number.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone, active]
              properties:
                phone:
                  type: string
                  description: Phone number or JID to tag
                  example: '447835156367'
                active:
                  type: boolean
                  description: '`true` to pause bot, `false` to resume'
                  example: true
      responses:
        '200':
          description: Handoff updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  jid: { type: string }
                  active: { type: boolean }
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Handoff]
      summary: Clear all human handoff tags
      description: Resumes bot mode for all chats on this instance.
      responses:
        '200':
          description: All handoffs cleared
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  cleared: { type: integer, example: 3 }
        '404':
          $ref: '#/components/responses/NotFound'

  /api/instances/{instanceId}/handoff/settings:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Handoff]
      summary: Get handoff settings
      description: |
        Returns the per-instance handoff configuration: which keywords resume
        the AI agent and what auto-reply message (if any) is sent on resume.
      responses:
        '200':
          description: Handoff settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  settings:
                    $ref: '#/components/schemas/HandoffSettings'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Handoff]
      summary: Update handoff settings
      description: |
        Change the resume keywords and/or auto-reply message for this instance.
        Settings are persisted across restarts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                resumeKeywords:
                  type: array
                  items: { type: string }
                  description: Keywords that resume the AI agent (e.g. `["#ai", "#resume"]`)
                  example: ['#ai', '#assistant', '#bot', '#resume']
                resumeMessage:
                  type: string
                  description: |
                    Auto-reply sent to the chat when the AI agent resumes.
                    Set to empty string `""` for silent resume.
                  example: 'AI assistant is back online. How can I help?'
      responses:
        '200':
          description: Updated handoff settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  settings:
                    $ref: '#/components/schemas/HandoffSettings'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

  # ==========================================
  # MEDIA / STORAGE
  # ==========================================
  /api/storage/status:
    get:
      tags: [System]
      summary: Check Azure Blob Storage status
      description: Returns whether media storage is enabled and the container name.
      responses:
        '200':
          description: Storage status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  enabled: { type: boolean, example: true }
                  container: { type: string, example: whatsapp-media }

  /api/upload:
    post:
      tags: [Messaging]
      summary: Upload media to Azure Blob Storage
      description: |
        Upload a file via base64-encoded payload. Returns a public URL that can
        be used as `mediaUrl` when sending messages, or stored for reference.
        Requires `AZURE_STORAGE_CONNECTION_STRING` to be configured.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [data, mimeType]
              properties:
                data:
                  type: string
                  format: byte
                  description: Base64-encoded file data
                mimeType:
                  type: string
                  description: MIME type of the file
                  example: image/jpeg
                fileName:
                  type: string
                  description: Original file name (used for extension detection)
                  example: photo.jpg
                instanceId:
                  type: string
                  description: Instance ID to use as storage folder prefix
                  example: wa_abc123
      responses:
        '200':
          description: File uploaded
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  url:
                    type: string
                    description: Public URL of the uploaded file
                    example: 'https://whatsappmediastore.blob.core.windows.net/whatsapp-media/wa_abc/uploads/1706000000-a1b2c3d4.jpg'
                  blobName:
                    type: string
                    example: wa_abc/uploads/1706000000-a1b2c3d4.jpg
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          description: Azure Blob Storage not configured

  # ==========================================
  # PROFILE
  # ==========================================
  /api/instances/{instanceId}/profile:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Profile]
      summary: Get profile info
      description: Get current phone number and connection state
      responses:
        '200':
          description: Profile info
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  profile:
                    type: object
                    properties:
                      phone:
                        type: string
                        nullable: true
                        example: '447393002183'
                      connected:
                        type: boolean
                        example: true
        '404':
          $ref: '#/components/responses/NotFound'

  /api/instances/{instanceId}/profile/name:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    put:
      tags: [Profile]
      summary: Update display name
      description: Change the WhatsApp push name visible to all contacts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  example: Acme Support
      responses:
        '200':
          description: Name updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Display name updated to "Acme Support"
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/profile/picture:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    put:
      tags: [Profile]
      summary: Set profile picture
      description: Set profile picture from a publicly accessible image URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [imageUrl]
              properties:
                imageUrl:
                  type: string
                  format: uri
                  example: https://example.com/logo.png
      responses:
        '200':
          description: Picture updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Profile picture updated
        '400':
          $ref: '#/components/responses/BadRequest'
    delete:
      tags: [Profile]
      summary: Remove profile picture
      description: Remove the current profile picture
      responses:
        '200':
          description: Picture removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Profile picture removed
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/profile/status:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    put:
      tags: [Profile]
      summary: Update "About" text
      description: Change the WhatsApp "About" / status text
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status:
                  type: string
                  example: We reply within minutes!
      responses:
        '200':
          description: About text updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: About text updated to "We reply within minutes!"
        '400':
          $ref: '#/components/responses/BadRequest'

  # ==========================================
  # BEHAVIOR SETTINGS
  # ==========================================
  /api/instances/{instanceId}/behavior:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    
    get:
      tags: [Behavior]
      summary: Get behavior settings
      description: |
        Returns the selected behavior profile plus advanced controls.

        Profiles:
        - `bot-native`: active-device automation with typing, read receipts,
          human-like delays, and presence cycling.
        - `notification-balanced`: forwards/logs inbound messages immediately,
          then waits a grace window before read/typing/reply and returns offline
          where possible. Notifications are high-confidence, not guaranteed.
        - `notification-max`: maximizes handset alerts by disabling typing and
          automated read receipts, with a silent grace window before replies.
      responses:
        '200':
          description: Behavior settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  behaviorSettings:
                    $ref: '#/components/schemas/BehaviorSettings'
        '404':
          $ref: '#/components/responses/NotFound'
    
    put:
      tags: [Behavior]
      summary: Update behavior settings
      description: |
        Same fields as the dashboard Behavior Profile card.
        Send any subset of keys; omitted keys are unchanged.

        Prefer `behaviorProfile` for new integrations. The legacy
        `phoneNotificationsEnabled`, `typingSimulation`, and `delayEnabled`
        fields are still accepted for compatibility.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BehaviorSettings'
      responses:
        '200':
          description: Settings updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  behaviorSettings:
                    $ref: '#/components/schemas/BehaviorSettings'
                  message:
                    type: string
                    example: Behavior settings updated
        '400':
          $ref: '#/components/responses/BadRequest'

  # ==========================================
  # ANTI-BAN
  # ==========================================
  /api/instances/{instanceId}/anti-ban:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    
    get:
      tags: [Anti-Ban]
      summary: Get anti-ban status
      description: Get current anti-ban settings and usage statistics
      responses:
        '200':
          description: Anti-ban status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  settings:
                    $ref: '#/components/schemas/AntiBanSettings'
                  health:
                    $ref: '#/components/schemas/AntiBanHealth'
        '404':
          $ref: '#/components/responses/NotFound'
    
    put:
      tags: [Anti-Ban]
      summary: Update anti-ban settings
      description: Update rate limits using preset or custom values
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                preset:
                  type: string
                  enum: [new, balanced, higher, custom]
                  description: Use a preset configuration
                  example: balanced
                messagesPerHour:
                  type: integer
                  description: Max messages per hour (for custom preset)
                  example: 50
                messagesPerDay:
                  type: integer
                  description: Max messages per day (for custom preset)
                  example: 300
                uniqueChatsPerHour:
                  type: integer
                  description: Max unique chats per hour (for custom preset)
                  example: 25
                uniqueChatsPerDay:
                  type: integer
                  description: Max unique chats per day (for custom preset)
                  example: 100
      responses:
        '200':
          description: Settings updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  settings:
                    $ref: '#/components/schemas/AntiBanSettings'
                  health:
                    $ref: '#/components/schemas/AntiBanHealth'
        '400':
          $ref: '#/components/responses/BadRequest'

  # ==========================================
  # WEBHOOK CONFIGURATION
  # ==========================================
  /api/webhook:
    get:
      tags: [Webhook]
      summary: Get global default webhook
      description: Get the global default webhook URL (from environment)
      responses:
        '200':
          description: Webhook configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  defaultWebhookUrl:
                    type: string
                    nullable: true
                    example: https://n8n.example.com/webhook/whatsapp
                  message:
                    type: string

  /api/instances/{instanceId}/webhook:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    
    get:
      tags: [Webhook]
      summary: Get instance webhook
      description: Get webhook configuration for a specific instance
      responses:
        '200':
          description: Webhook configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  instanceWebhookUrl:
                    type: string
                    nullable: true
                    description: Instance-specific webhook (null if using global)
                  effectiveWebhookUrl:
                    type: string
                    nullable: true
                    description: The actual webhook URL that will be used
                  usingGlobalDefault:
                    type: boolean
                    description: Whether using the global default webhook
        '404':
          $ref: '#/components/responses/NotFound'
    
    put:
      tags: [Webhook]
      summary: Set instance webhook
      description: Set a custom webhook URL for this instance, or clear to use global default
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                webhookUrl:
                  type: string
                  nullable: true
                  description: Webhook URL or null/empty to use global default
                  example: https://your-api.com/webhook/instance-1
      responses:
        '200':
          description: Webhook updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  instanceWebhookUrl:
                    type: string
                    nullable: true
                  effectiveWebhookUrl:
                    type: string
                    nullable: true
                  usingGlobalDefault:
                    type: boolean
                  message:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'

  /api/instances/{instanceId}/webhook/test:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    post:
      tags: [Whitelabel, Webhook]
      summary: Test webhook delivery
      description: |
        Sends a synthetic test payload to the instance's configured webhook URL
        and returns the HTTP response. Use this to verify webhook reachability
        before going live.
      responses:
        '200':
          description: Webhook test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: true if webhook returned 2xx
                    example: true
                  webhookUrl:
                    type: string
                    example: https://acme.com/webhook
                  responseStatus:
                    type: integer
                    description: HTTP status returned by the webhook
                    example: 200
                  responseBody:
                    description: First 500 chars of the webhook's response body
                  message:
                    type: string
                    example: Webhook test delivered successfully
              examples:
                success:
                  summary: Webhook reachable
                  value:
                    success: true
                    webhookUrl: https://acme.com/webhook
                    responseStatus: 200
                    responseBody: { "ok": true }
                    message: Webhook test delivered successfully
                failure:
                  summary: Webhook unreachable
                  value:
                    success: false
                    webhookUrl: https://acme.com/webhook
                    error: connect ECONNREFUSED
                    message: Failed to deliver test webhook
        '400':
          description: No webhook configured
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: No webhook URL configured for this instance
                  hint:
                    type: string
                    example: Set webhookUrl via PUT /api/instances/:id or during onboarding
        '404':
          $ref: '#/components/responses/NotFound'

  # ==========================================
  # LOGS
  # ==========================================
  /api/instances/{instanceId}/logs:
    parameters:
      - $ref: '#/components/parameters/instanceId'
    get:
      tags: [Logs]
      summary: Get activity logs
      description: Get recent activity logs for instance
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          description: Number of log entries to return
      responses:
        '200':
          description: Activity logs
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  logs:
                    type: array
                    items:
                      $ref: '#/components/schemas/LogEntry'
        '404':
          $ref: '#/components/responses/NotFound'

  # ==========================================
  # SYSTEM
  # ==========================================
  /api/health:
    get:
      tags: [System]
      summary: Health check
      description: Check server health and instance counts
      security: []
      responses:
        '200':
          description: Server healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  uptime:
                    type: number
                    description: Server uptime in seconds
                    example: 3600.5
                  instances:
                    type: object
                    properties:
                      total:
                        type: integer
                        example: 3
                      connected:
                        type: integer
                        example: 2

  /api/status:
    get:
      tags: [System]
      summary: System status
      description: Get overall system status with all instances
      responses:
        '200':
          description: System status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  instanceCount:
                    type: integer
                    example: 2
                  instances:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: wa_xxx
                        name:
                          type: string
                          example: Customer Support
                        status:
                          type: string
                          enum: [disconnected, connecting, connected]
                        phone:
                          type: string
                          nullable: true
                          example: '60123456789'

  /api/system/reload-behavior-from-disk:
    post:
      tags: [System]
      summary: Hot-reload behavior settings from disk
      description: |
        Re-reads `instances/instances.json` and applies each row's `behaviorSettings` onto
        already-loaded instances. **Does not** restart Node, reconnect sockets, or reload code.
        Use when you edited the JSON on disk (or synced it) and want live instances to pick up
        behaviour flags without `pm2 reload`. Alternatively send `SIGHUP` with env
        `WASUP_SIGHUP_BEHAVIOR_RELOAD=1` (same handler).
      responses:
        '200':
          description: Reload outcome per instance id
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  count:
                    type: integer
                  results:
                    type: array
                    items:
                      type: object
        '503':
          description: Server still starting

  /api/generate-api-key:
    post:
      tags: [System]
      summary: Generate API key
      description: Generate a new random API key (for reference only, add to .env manually)
      responses:
        '200':
          description: API key generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  apiKey:
                    type: string
                    example: a1b2c3d4e5f6...
                  message:
                    type: string
                    example: Add this to your .env file as API_KEY=<key>

  # ==========================================
  # WASUP ANTI-BAN (/api/instances/:id/antiban-v2)
  # ==========================================
  /api/instances/{instanceId}/antiban-v2:
    get:
      tags: [Wasup Anti-Ban]
      summary: Get full v2 status
      description: |
        Returns the complete anti-ban v2 state for this instance:

        - **config** — preset, overrides, module flags, alerts webhook
        - **health** — risk level (low/medium/high/critical), score 0-100, recommendation, isPaused
        - **warmup** — phase (warming/graduated), day x/N, today's limit + sent, progress %
        - **rateLimiter** — last-minute / hour / day counts vs limits
        - **retryTracker** — total retries, spirals detected, active retries
        - **sessionStability** — Bad MAC count, isDegraded
        - **stats** — full unfiltered Wasup anti-ban runtime stats blob

        Returns `running: false` when the instance is disconnected (stats unavailable until reconnect).
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: V2 status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  antibanV2:
                    type: object
                    properties:
                      enabled: { type: boolean }
                      running: { type: boolean }
                      preset: { type: string, enum: [conservative, moderate, aggressive] }
                      health:
                        type: object
                        properties:
                          risk: { type: string, enum: [low, medium, high, critical] }
                          score: { type: integer, minimum: 0, maximum: 100 }
                          recommendation: { type: string }
                          isPaused: { type: boolean }
                      warmup:
                        type: object
                        properties:
                          phase: { type: string, enum: [warming, graduated] }
                          day: { type: integer }
                          totalDays: { type: integer }
                          todayLimit: { type: integer }
                          todaySent: { type: integer }
                          progress: { type: integer }
                          complete: { type: boolean }

  /api/instances/{instanceId}/antiban-v2/config:
    get:
      tags: [Wasup Anti-Ban]
      summary: Get v2 config block
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Current config
    put:
      tags: [Wasup Anti-Ban]
      summary: Update v2 config
      description: |
        Update preset / overrides / module flags. Most fields take effect on next reconnect.
        Rate-limit overrides hot-reload when possible.

        Body fields are all optional — supply only what you want to change.
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                  description: Master switch. Set false to fall back to legacy AntiBanManager.
                preset:
                  type: string
                  enum: [conservative, moderate, aggressive]
                overrides:
                  type: object
                  description: Override individual rate limits + delays
                  properties:
                    maxPerMinute: { type: integer, example: 12 }
                    maxPerHour: { type: integer, example: 250 }
                    maxPerDay: { type: integer, example: 4000 }
                    minDelayMs: { type: integer, example: 1500 }
                    maxDelayMs: { type: integer, example: 5000 }
                modules:
                  type: object
                  description: Per-module enable/disable flags
                  properties:
                    warmup:           { type: object, properties: { enabled: { type: boolean } } }
                    replyRatio:       { type: object, properties: { enabled: { type: boolean } } }
                    contactGraph:     { type: object, properties: { enabled: { type: boolean } } }
                    presence:
                      type: object
                      properties:
                        enabled: { type: boolean }
                        circadianProfile:
                          type: string
                          enum: [default, nightOwl, earlyBird, always_on]
                        timezone: { type: string, example: 'Europe/London' }
                    retryTracker:     { type: object, properties: { enabled: { type: boolean } } }
                    reconnectThrottle: { type: object, properties: { enabled: { type: boolean } } }
                    lidResolver:      { type: object, properties: { enabled: { type: boolean } } }
                    sessionStability: { type: object, properties: { enabled: { type: boolean } } }
                    stealthConnect:   { type: object, properties: { enabled: { type: boolean } } }
                alertsWebhook:
                  type: string
                  nullable: true
                  description: Optional webhook URL fired on risk transitions (Telegram-/Discord-compatible JSON)
      responses:
        '200':
          description: Updated

  /api/instances/{instanceId}/antiban-v2/health:
    get:
      tags: [Wasup Anti-Ban]
      summary: Compact health (risk + score)
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Risk
          content:
            application/json:
              schema:
                type: object
                properties:
                  health:
                    type: object
                    properties:
                      risk: { type: string, enum: [low, medium, high, critical] }
                      score: { type: integer }
                      recommendation: { type: string }
                      isPaused: { type: boolean }

  /api/instances/{instanceId}/antiban-v2/warmup:
    get:
      tags: [Wasup Anti-Ban]
      summary: Warmup state (day x/7)
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Warmup
          content:
            application/json:
              schema:
                type: object
                properties:
                  warmup:
                    type: object
                    properties:
                      phase: { type: string, enum: [warming, graduated] }
                      day: { type: integer, example: 4 }
                      totalDays: { type: integer, example: 7 }
                      todayLimit: { type: integer, example: 117 }
                      todaySent: { type: integer, example: 23 }
                      progress: { type: integer, example: 57 }
                      complete: { type: boolean }

  /api/instances/{instanceId}/antiban-v2/lid-mappings:
    get:
      tags: [Wasup Anti-Ban]
      summary: LID↔PN cache snapshot
      description: |
        Returns the current LID/PhoneNumber mapping cache used to fix Bad MAC /
        No Session errors. Mappings are auto-learned from inbound events.
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Mappings

  /api/instances/{instanceId}/antiban-v2/pause:
    post:
      tags: [Wasup Anti-Ban]
      summary: Emergency pause
      description: All sends are blocked until /resume. Useful when you suspect a ban is imminent.
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Paused

  /api/instances/{instanceId}/antiban-v2/resume:
    post:
      tags: [Wasup Anti-Ban]
      summary: Resume after pause
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Resumed

  /api/instances/{instanceId}/antiban-v2/reset:
    post:
      tags: [Wasup Anti-Ban]
      summary: Nuclear reset
      description: |
        Wipes warmup, rate-limit history, health stats, retry-tracker. Stealth
        fingerprint + LID mappings are preserved. Use after serving a real ban period.
      parameters:
        - name: instanceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Reset

# ==========================================
# COMPONENTS
# ==========================================
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer

  parameters:
    instanceId:
      name: instanceId
      in: path
      required: true
      schema:
        type: string
      description: Instance ID
      example: wa_lxyz123_abc45

  schemas:
    SendMessageBody:
      type: object
      required: [to]
      properties:
        to:
          type: string
          description: Recipient phone number (with country code, no + or spaces) or JID
          example: '60123456789'
        message:
          type: string
          description: Text body or caption (required for text type, optional caption for media)
          example: Hello! How can I help you today?
        messageType:
          type: string
          description: Type of message to send
          enum: [text, image, video, document, audio, buttons, list, location, contact]
          default: text
          example: text
        mediaUrl:
          type: string
          description: Direct URL to media file (required for image/video/document/audio)
          example: 'https://example.com/photo.jpg'
        mimeType:
          type: string
          description: MIME type for document/audio
          example: application/pdf
        fileName:
          type: string
          description: File name for document messages
          example: report.pdf
        ptt:
          type: boolean
          description: Send audio as voice note (push-to-talk). Default true.
          default: true
        footer:
          type: string
          description: Footer text for button and list messages
          example: Powered by WhatsApp AI
        buttons:
          type: array
          description: |
            Quick-reply buttons (messageType=buttons). Uses Wasup's interactive helper so
            native tappable buttons render on iOS and Android.
          items:
            type: object
            properties:
              id:
                type: string
                example: btn_yes
              text:
                type: string
                example: 'Yes'
          example:
            - { id: 'yes', text: 'Yes, proceed' }
            - { id: 'no', text: 'No thanks' }
            - { id: 'later', text: 'Maybe later' }
        title:
          type: string
          description: Title for list messages
          example: Our Services
        buttonText:
          type: string
          description: Menu button label for list messages
          default: Menu
          example: View Options
        sections:
          type: array
          description: |
            List sections (messageType=list). Rendered as a single-select native flow
            via Wasup's interactive helper.
          items:
            type: object
            properties:
              title:
                type: string
                example: Services
              rows:
                type: array
                items:
                  type: object
                  properties:
                    title:
                      type: string
                      example: Consulting
                    id:
                      type: string
                      example: consulting
                    description:
                      type: string
                      example: Expert advice
        latitude:
          type: number
          format: double
          description: Latitude for location messages
          example: 3.1577
        longitude:
          type: number
          format: double
          description: Longitude for location messages
          example: 101.7117
        locationName:
          type: string
          description: Location name for location messages
          example: Petronas Twin Towers
        locationAddress:
          type: string
          description: Address for location messages
          example: KLCC, Kuala Lumpur
        contactCard:
          type: object
          description: Contact card details (for messageType=contact)
          properties:
            displayName:
              type: string
              example: John Doe
            phoneNumber:
              type: string
              example: '+60123456789'
        typingSimulation:
          type: boolean
          description: Override instance typing simulation setting for this message
        delayEnabled:
          type: boolean
          description: Override instance human-delay setting for this message
        contactName:
          type: string
          description: Name for auto-saving contact before sending
          example: Unknown User
        skipContactSave:
          type: boolean
          description: Skip auto-saving recipient as a contact
          default: false

    ReactionBody:
      type: object
      required: [to, messageId, emoji]
      properties:
        to:
          type: string
          description: Recipient phone number or JID (for instance-scoped endpoint)
          example: '447835156367'
        to_phone:
          type: string
          description: Recipient phone number (for auto-select endpoint)
          example: '447835156367'
        messageId:
          type: string
          description: |
            WhatsApp message id to react to. Obtain from the `message_id` field
            returned by the send endpoints or from inbound webhook payloads.
          example: '3EB0DAC5F4A2E1B7'
        emoji:
          type: string
          description: Emoji to react with. Pass an empty string `""` to remove a reaction.
          example: '🔥'
        fromMe:
          type: boolean
          description: |
            Set to `true` if the target message was sent by the connected account.
            Defaults to `false` (reacting to a received message).
          default: false

    HandoffSettings:
      type: object
      properties:
        resumeKeywords:
          type: array
          items: { type: string }
          description: |
            Keywords a human agent types from the phone to reactivate the AI bot
            for that chat. Matched case-insensitively at the start of the message.
          default: ['#ai', '#assistant', '#bot', '#resume']
          example: ['#ai', '#assistant', '#bot', '#resume']
        resumeMessage:
          type: string
          description: |
            Optional auto-reply sent when the AI agent resumes on a chat.
            Empty string means silent resume.
          default: ''
          example: 'AI assistant is back online. How can I help?'

    OnboardResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        instanceId:
          type: string
          example: wa_abc123
        pairingCode:
          type: string
          nullable: true
          description: Pairing code to enter in WhatsApp (null if already connected)
          example: 'A1B2-C3D4'
        status:
          type: string
          enum: [disconnected, connecting, connected]
          example: connecting
        message:
          type: string
          example: 'Enter code A1B2-C3D4 in WhatsApp > Linked Devices > Link a Device'

    ConnectionStatus:
      type: object
      properties:
        success:
          type: boolean
          example: true
        status:
          type: string
          enum: [disconnected, connecting, connected]
          example: connected
        phone:
          type: string
          nullable: true
          example: '447393002183'
        connectedAt:
          type: string
          format: date-time
          nullable: true
        uptime:
          type: integer
          nullable: true
          description: Seconds since connection was established
          example: 3600
        pairingCode:
          type: string
          nullable: true
        qrCode:
          type: string
          nullable: true

    Message:
      type: object
      properties:
        id:
          type: string
          example: '3EB0B430A7F9B1D2'
        direction:
          type: string
          enum: [inbound, outbound]
          example: inbound
        from:
          type: string
          description: Sender phone number
          example: '447393002183'
        to:
          type: string
          description: Recipient phone number
          example: '60123456789'
        text:
          type: string
          example: Hello, I need help with my order
        mediaUrl:
          type: string
          nullable: true
          description: |
            Public URL of the media file (image, audio, video, document, sticker)
            stored in Azure Blob Storage. Only present for media messages when
            `AZURE_STORAGE_CONNECTION_STRING` is configured.
          example: 'https://whatsappmediastore.blob.core.windows.net/whatsapp-media/wa_abc/image/1706000000-a1b2c3d4.jpg'
        mediaType:
          type: string
          nullable: true
          enum: [image, video, audio, document, sticker, null]
          description: Type of media attachment
        mimeType:
          type: string
          nullable: true
          description: MIME type of the media file
          example: 'image/jpeg'
        fileName:
          type: string
          nullable: true
          description: Original file name (documents only)
        timestamp:
          type: string
          format: date-time
        status:
          type: string
          example: delivered

    WebhookPayload:
      type: object
      description: |
        Payload sent to the per-instance webhook URL when a message is received.
        Your webhook should return `{ reply: "text" }` to auto-respond, or
        `{ skip: true }` to suppress the bot reply.
      properties:
        message_id: { type: string }
        created_at: { type: string, format: date-time }
        from_phone: { type: string, example: '447835156367' }
        to_phone: { type: string, example: '358942721757' }
        message: { type: string }
        media_type:
          type: string
          enum: [text, image, video, audio, document, sticker, location, contact]
        media_url:
          type: string
          nullable: true
          description: Public Azure Blob Storage URL of the media file (null for text)
        mime_type: { type: string, nullable: true }
        file_name: { type: string, nullable: true }
        status: { type: string, example: received }
        webhook_id: { type: string, description: Instance ID }
        event: { type: string, example: message }
        quoted_message: { type: string, nullable: true }

    Instance:
      type: object
      properties:
        id:
          type: string
          example: wa_lxyz123_abc45
        name:
          type: string
          example: Customer Support
        status:
          type: string
          enum: [disconnected, connecting, connected]
          example: connected
        qrCode:
          type: string
          nullable: true
          description: Base64 QR code (only when connecting)
        pairingCode:
          type: string
          nullable: true
          description: Pairing code (only when connecting via pairing)
        connectedPhone:
          type: string
          nullable: true
          example: '60123456789'
        connectedAt:
          type: string
          format: date-time
          nullable: true
        webhookUrl:
          type: string
          example: https://your-api.com/webhook
        behaviorSettings:
          $ref: '#/components/schemas/BehaviorSettings'
        antiBanSettings:
          $ref: '#/components/schemas/AntiBanSettings'
        antiBanHealth:
          $ref: '#/components/schemas/AntiBanHealth'
        createdAt:
          type: string
          format: date-time

    BehaviorSettings:
      type: object
      properties:
        behaviorProfile:
          type: string
          enum: [bot-native, notification-balanced, notification-max]
          description: |
            Top-level behavior mode.

            `bot-native` uses active-device automation with typing, reads, delays,
            and presence cycling.

            `notification-balanced` prioritizes handset alerts without promising
            delivery: inbound messages are logged/webhooked immediately, then
            reads/typing/replies wait for `notificationGraceMs`.

            `notification-max` maximizes handset alerts: typing is disabled and
            automated read receipts are skipped.
          default: bot-native
          example: notification-balanced
        typingSimulation:
          type: boolean
          description: |
            Show "typing..." before sending. In `notification-balanced`, typing
            happens only after the grace window. In `notification-max`, this is
            forced off.
          default: true
          example: true
        delayEnabled:
          type: boolean
          description: Add human-like delays before responding
          default: true
          example: true
        notificationGraceMs:
          type: integer
          minimum: 0
          maximum: 120000
          description: |
            Milliseconds to wait after an inbound message before read receipt,
            typing, or reply in notification profiles. The webhook/log event is
            still sent immediately.
          default: 12000
          example: 12000
        phoneNotificationsEnabled:
          type: boolean
          description: |
            Legacy compatibility flag. New clients should set `behaviorProfile`.
            When true, maps to `notification-balanced` unless typing is false,
            in which case it maps to `notification-max`.
          default: false
          example: false

    AntiBanSettings:
      type: object
      properties:
        preset:
          type: string
          enum: [conservative, balanced, aggressive, custom]
          example: balanced
        messagesPerHour:
          type: integer
          example: 200
        messagesPerDay:
          type: integer
          example: 5000
        uniqueChatsPerHour:
          type: integer
          example: 50
        uniqueChatsPerDay:
          type: integer
          example: 500

    AntiBanHealth:
      type: object
      properties:
        status:
          type: string
          enum: [healthy, warning, limited]
          example: healthy
        hourlyUsage:
          type: integer
          description: Percentage of hourly message limit used
          example: 25
        dailyUsage:
          type: integer
          description: Percentage of daily message limit used
          example: 10
        hourlyChatsUsage:
          type: integer
          example: 20
        dailyChatsUsage:
          type: integer
          example: 8
        warnings:
          type: array
          items:
            type: string
          example: []
        stats:
          type: object
          properties:
            messagesThisHour:
              type: integer
              example: 12
            messagesThisDay:
              type: integer
              example: 30
            uniqueChatsThisHour:
              type: integer
              example: 5
            uniqueChatsThisDay:
              type: integer
              example: 8

    SendResult:
      type: object
      properties:
        sent:
          type: boolean
          example: true
        delay:
          type: integer
          description: Delay applied in milliseconds
          example: 3500
        typingSimulation:
          type: boolean
          example: true
        delayEnabled:
          type: boolean
          example: true
        reason:
          type: string
          description: Reason if message was blocked
        waitTime:
          type: integer
          description: Time to wait before retry (if rate limited)

    LogEntry:
      type: object
      properties:
        id:
          type: string
          example: '1704825600000'
        timestamp:
          type: string
          format: date-time
        message:
          type: string
          example: Connected as 60123456789
        level:
          type: string
          enum: [info, success, warning, error]
          example: success

    Error:
      type: object
      properties:
        error:
          type: string
          example: Instance not found

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Unauthorized
              message:
                type: string
                example: Valid API key required. Use X-API-Key header or Authorization Bearer
