openapi: 3.1.0
info:
  title: WhatsApp Adapter API
  version: v1
  summary: Provider-agnostic messaging API backed by the local adapter runtime.
  description: >
    Version 1 exposes adapter-owned sessions, conversations, participants, and messages.
    The current implementation is backed by WAHA, but the public contract is intentionally
    normalized so the provider can change later without reshaping the API surface.
servers:
  - url: https://whatsapp-adapter-api.mathbox.90.cz
    description: Assigned production hostname on mathbox; reverse proxy and TLS still need configuration per python-application-skill before external use
  - url: http://127.0.0.1:8100
    description: Local development
tags:
  - name: Health
    description: Service health and readiness signals
  - name: Sessions
    description: Signed-in messaging identities known to the adapter
  - name: Conversations
    description: Group or channel-like message destinations for a session
  - name: Messages
    description: Message history and message sending
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API token
  schemas:
    ErrorResponse:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
      required:
        - error
    HealthResponse:
      type: object
      properties:
        status:
          type: string
      required:
        - status
    Identity:
      type: object
      properties:
        id:
          type: string
        display_name:
          type: string
        phone_number:
          type:
            - string
            - "null"
        address:
          type:
            - string
            - "null"
      required:
        - id
        - display_name
    SessionCapabilities:
      type: object
      properties:
        list_conversations:
          type: boolean
        send_message:
          type: boolean
        list_participants:
          type: boolean
        list_messages:
          type: boolean
        attachments:
          type: boolean
      required:
        - list_conversations
        - send_message
        - list_participants
        - list_messages
        - attachments
    Session:
      type: object
      properties:
        id:
          type: string
        provider:
          type: string
        status:
          type: string
          description: Normalized adapter session state.
        provider_status:
          type: string
          description: Raw provider-reported status for debugging and operations.
        description:
          type: string
        identity:
          oneOf:
            - $ref: "#/components/schemas/Identity"
            - type: "null"
        capabilities:
          $ref: "#/components/schemas/SessionCapabilities"
      required:
        - id
        - provider
        - status
        - provider_status
        - description
        - capabilities
    SessionEnvelope:
      type: object
      properties:
        session:
          $ref: "#/components/schemas/Session"
      required:
        - session
    SessionListEnvelope:
      type: object
      properties:
        sessions:
          type: array
          items:
            $ref: "#/components/schemas/Session"
      required:
        - sessions
    Conversation:
      type: object
      properties:
        id:
          type: string
        session_id:
          type: string
        type:
          type: string
        name:
          type: string
        description:
          type: string
        member_count:
          type: integer
        last_synced_at:
          type: string
          format: date-time
      required:
        - id
        - session_id
        - type
        - name
        - description
        - member_count
        - last_synced_at
    ConversationEnvelope:
      type: object
      properties:
        conversation:
          $ref: "#/components/schemas/Conversation"
      required:
        - conversation
    ConversationListEnvelope:
      type: object
      properties:
        conversations:
          type: array
          items:
            $ref: "#/components/schemas/Conversation"
      required:
        - conversations
    Participant:
      type: object
      properties:
        id:
          type: string
        display_name:
          type: string
        phone_number:
          type:
            - string
            - "null"
        address:
          type:
            - string
            - "null"
        role:
          type: string
      required:
        - id
        - display_name
        - role
    ParticipantListEnvelope:
      type: object
      properties:
        participants:
          type: array
          items:
            $ref: "#/components/schemas/Participant"
      required:
        - participants
    MessageSender:
      type: object
      properties:
        participant_id:
          type: string
        display_name:
          type: string
        phone_number:
          type:
            - string
            - "null"
        address:
          type:
            - string
            - "null"
      required:
        - participant_id
        - display_name
    Attachment:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
        file_name:
          type:
            - string
            - "null"
        mime_type:
          type:
            - string
            - "null"
        url:
          type:
            - string
            - "null"
          format: uri
        preview_url:
          type:
            - string
            - "null"
        width:
          type:
            - integer
            - "null"
        height:
          type:
            - integer
            - "null"
        size_bytes:
          oneOf:
            - type: integer
            - type: string
            - type: "null"
      required:
        - id
        - kind
        - url
        - preview_url
        - width
        - height
    MessageStatus:
      type: object
      properties:
        ack:
          oneOf:
            - type: integer
            - type: string
            - type: "null"
        ack_name:
          type:
            - string
            - "null"
    MessageReaction:
      type: object
      properties:
        emoji:
          type: string
        actor:
          $ref: "#/components/schemas/MessageSender"
        updated_at:
          type:
            - string
            - "null"
          format: date-time
      required:
        - emoji
        - actor
    Message:
      type: object
      properties:
        id:
          type: string
        conversation_id:
          type: string
        direction:
          type: string
        sender:
          $ref: "#/components/schemas/MessageSender"
        text:
          type: string
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/Attachment"
        posted_at:
          type:
            - string
            - "null"
          format: date-time
        accepted_at:
          type:
            - string
            - "null"
          format: date-time
        status:
          $ref: "#/components/schemas/MessageStatus"
        reactions:
          type: array
          items:
            $ref: "#/components/schemas/MessageReaction"
      required:
        - id
        - conversation_id
        - direction
        - sender
        - text
        - attachments
        - status
    MessageListEnvelope:
      type: object
      properties:
        messages:
          type: array
          items:
            $ref: "#/components/schemas/Message"
        next_since:
          type:
            - string
            - "null"
          format: date-time
      required:
        - messages
    SendMessageRequest:
      type: object
      properties:
        text:
          type: string
          minLength: 1
      required:
        - text
    SendMessageResponse:
      type: object
      properties:
        status:
          type: string
        session_id:
          type: string
        conversation_id:
          type: string
        message_id:
          type:
            - string
            - "null"
        accepted_at:
          type: string
          format: date-time
      required:
        - status
        - session_id
        - conversation_id
        - accepted_at
    ReactToMessageRequest:
      type: object
      properties:
        emoji:
          type: string
          description: Emoji reaction to apply. Send an empty string to remove an existing reaction.
      required:
        - emoji
    ReactToMessageResponse:
      type: object
      properties:
        status:
          type: string
        session_id:
          type: string
        conversation_id:
          type: string
        message_id:
          type: string
        emoji:
          type: string
        accepted_at:
          type: string
          format: date-time
      required:
        - status
        - session_id
        - conversation_id
        - message_id
        - emoji
        - accepted_at
  responses:
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            missingToken:
              value:
                error: unauthorized
    Forbidden:
      description: Valid auth mechanism but caller is not allowed to use the endpoint.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
paths:
  /healthz:
    get:
      tags:
        - Health
      summary: Check service health
      operationId: getHealth
      security: []
      responses:
        "200":
          description: Service and provider dependency are healthy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ok
        "503":
          description: Service is running but the provider dependency is unavailable.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  reason:
                    type: string
  /api/v1/session:
    get:
      tags:
        - Sessions
      summary: Read the default adapter session
      operationId: getDefaultSession
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Default session state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/session/ensure:
    post:
      tags:
        - Sessions
      summary: Ensure the default adapter session exists and is started
      operationId: ensureDefaultSession
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Default session is ensured and returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/sessions:
    get:
      tags:
        - Sessions
      summary: List signed-in sessions
      operationId: listSessions
      security:
        - bearerAuth: []
      responses:
        "200":
          description: All sessions known to the adapter.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionListEnvelope"
              examples:
                default:
                  value:
                    sessions:
                      - id: default
                        provider: whatsapp
                        status: connected
                        provider_status: WORKING
                        description: Robot (+420601290297)
                        identity:
                          id: ident_1f3d0e7a0ab2
                          display_name: Robot
                          phone_number: "+420601290297"
                          address: "420601290297@c.us"
                        capabilities:
                          list_conversations: true
                          send_message: true
                          list_participants: true
                          list_messages: true
                          attachments: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/sessions/{sessionId}:
    get:
      tags:
        - Sessions
      summary: Read one session
      operationId: getSession
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Session state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/sessions/{sessionId}/ensure:
    post:
      tags:
        - Sessions
      summary: Ensure one session exists and is started
      operationId: ensureSession
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Session is ensured and returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/sessions/{sessionId}/conversations:
    get:
      tags:
        - Conversations
      summary: List conversations for a session
      operationId: listConversations
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Conversations visible to the selected session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationListEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/sessions/{sessionId}/conversations/{conversationId}:
    get:
      tags:
        - Conversations
      summary: Read one conversation
      operationId: getConversation
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: One normalized conversation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Conversation not found.
  /api/v1/sessions/{sessionId}/conversations/{conversationId}/participants:
    get:
      tags:
        - Conversations
      summary: List participants for a conversation
      operationId: listParticipants
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Normalized conversation participants.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ParticipantListEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Conversation not found.
  /api/v1/sessions/{sessionId}/conversations/{conversationId}/messages:
    get:
      tags:
        - Messages
      summary: List messages for a conversation
      operationId: listMessages
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
        - name: since
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: Return only messages newer than this cursor.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: Messages plus the next polling cursor.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageListEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Conversation not found.
  /api/v1/sessions/{sessionId}/conversations/{conversationId}/messages/{messageId}/reaction:
    put:
      tags:
        - Messages
      summary: Apply or remove an emoji reaction for one message
      operationId: reactToMessage
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
        - name: messageId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReactToMessageRequest"
            examples:
              addReaction:
                value:
                  emoji: "👍"
              removeReaction:
                value:
                  emoji: ""
      responses:
        "202":
          description: Reaction request accepted by the adapter for provider delivery.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReactToMessageResponse"
        "400":
          description: Validation error.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Conversation not found.
    post:
      tags:
        - Messages
      summary: Send a message to a conversation
      operationId: sendMessage
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMessageRequest"
      responses:
        "202":
          description: Message accepted by the adapter for provider delivery.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendMessageResponse"
        "400":
          description: Validation error.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Conversation not found.
