openapi: 3.1.0
info:
  title: DM Champ API
  version: '1.0.0'
  description: |
    API documentation for DM Champ Cloud Functions.
    Protected endpoints require either an 'apiKey' or 'userId' query parameter for authentication.
    Some endpoints only accept 'apiKey' authentication.

    Example URLs:
    - Using API Key: https://api.dmchamp.com/v1/contacts?apiKey=your_api_key
    - Using User ID: https://api.dmchamp.com/v1/contacts?userId=your_user_id

servers:
  - url: https://api.dmchamp.com/v1
    description: Production API Server

security:
  - ApiKeyAuth: []
  - UserIdAuth: []

components:
  schemas:
    UsageLimits:
      type: object
      properties:
        monthly_credits:
          type: integer
          description: Total monthly credits allocated
        monthly_credits_used:
          type: integer
          description: Number of monthly credits used
        last_reset_date:
          type: string
          format: date-time
          description: Date when the credits were last reset
        notifications:
          type: ["object", "null"]
          description: Notification settings
        credits:
          type: integer
          description: Current credit balance
        roll_over_to_next_month:
          type: boolean
          description: Whether unused credits roll over to next month
    Contact:
      type: object
      required:
        - phoneNumber
      properties:
        phoneNumber:
          type: string
          description: Contact's phone number (must include valid country code)
        firstName:
          type: string
          description: Contact's first name
          default: "Unknown"
        lastName:
          type: string
          description: Contact's last name
          default: "Unknown"
        email:
          type: string
          description: Contact's email address
          format: email
          default: "unknown@unknown.com"
        is_bot_active:
          type: boolean
          description: Whether the bot is active for this contact
          default: true
        listId:
          type: string
          description: Single list ID to add the contact to upon creation
          examples: ["list123"]
        listIds:
          type: array
          items:
            type: string
          description: Multiple list IDs to add the contact to upon creation (takes precedence over listId)
          examples: [["list123", "list456"]]
        instagramId:
          type: ["string", "null"]
          description: Contact's Instagram ID
        currentCampaign:
          type: ["string", "null"]
          description: Current campaign identifier
        currentCampaignData:
          type: ["object", "null"]
          description: Data associated with current campaign
        channel:
          type: ["string", "null"]
          description: Communication channel
        isChatWindowOpen:
          type: boolean
          default: false
          description: Whether chat window is open
        externalId:
          type: ["string", "null"]
          description: External identifier
        customChannel:
          type: ["string", "null"]
          description: Custom channel identifier
        chatWidgetId:
          type: ["string", "null"]
          description: Chat widget identifier
        do_not_disturb:
          type: boolean
          default: false
          description: Whether the contact should not be disturbed
    SubAccount:
      type: object
      required:
        - email
        - first_name
        - last_name
        - usage_limits
      properties:
        email:
          type: string
          description: Sub-account user's email address
          format: email
        first_name:
          type: string
          description: Sub-account user's first name
        last_name:
          type: string
          description: Sub-account user's last name
        role:
          type: string
          description: Role of the sub-account (always "Sub Account")
          default: "Sub Account"
        is_agency_sub_account:
          type: boolean
          default: true
          description: Indicates this is a sub-account
        business_name:
          type: string
          description: Business name for the sub-account
        description:
          type: string
          description: Business description
        address_line:
          type: string
          description: Business address line
        city:
          type: string
          description: Business city
        country:
          type: string
          description: Business country
        postal_code:
          type: string
          description: Business postal code
        state:
          type: string
          description: Business state
        time_zone_id:
          type: string
          description: Time zone identifier
        language:
          type: string
          description: Preferred language
        usage_limits:
          type: object
          required:
            - monthly_credits
          properties:
            monthly_credits:
              type: integer
              description: Total monthly credits allocated
              minimum: 1
            monthly_credits_used:
              type: integer
              description: Number of monthly credits used
              default: 0
            credits:
              type: integer
              description: Current credit balance
            roll_over_to_next_month:
              type: boolean
              description: Whether unused credits roll over to next month
              default: false

    Tag:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Tag name
        description:
          type: string
          description: Tag description
    Message:
      type: object
      properties:
        content:
          type: string
          description: Message content
        timestamp:
          type: string
          format: date-time
        sender:
          type: string
        channel:
          type: string
    ChatMessage:
      type: object
      description: A message within a chat session
      properties:
        id:
          type: string
          description: Unique message identifier
        body:
          type: string
          description: Message body/content
        direction:
          type: string
          enum: [inbound, outbound]
          description: Message direction
        timestamp:
          type: ["string", "null"]
          format: date-time
          description: Message timestamp
        type:
          type: string
          description: Message type
        channel:
          type: string
          description: Channel the message was sent through
        status:
          type: string
          description: Message delivery status
    ChatSession:
      type: object
      description: A chat session between the user and a contact
      properties:
        id:
          type: string
          description: Unique session identifier
        start_date_time:
          type: string
          format: date-time
          description: Session start timestamp (ISO 8601)
        end_date_time:
          type: string
          format: date-time
          description: Session end timestamp (ISO 8601)
        status:
          type: string
          enum: [ChatSessionOpened, ChatSessionClosed]
          description: Current session status
        tag:
          type: ["string", "null"]
          description: Tag associated with this session
        messages:
          type: array
          description: Session messages. Only present when includeMessages=true
          items:
            $ref: '#/components/schemas/ChatMessage'
    RecentChatSession:
      type: object
      description: A recent chat session with contact details
      properties:
        session_id:
          type: string
          description: Unique session identifier
        contact_id:
          type: string
          description: Contact identifier
        contact_name:
          type: string
          description: Contact full name
        contact_phone:
          type: string
          description: Contact phone number
        contact_email:
          type: string
          description: Contact email address
        start_date_time:
          type: string
          format: date-time
          description: Session start timestamp (ISO 8601)
        end_date_time:
          type: string
          format: date-time
          description: Session end timestamp (ISO 8601)
        status:
          type: string
          enum: [ChatSessionOpened, ChatSessionClosed]
          description: Current session status
        tag:
          type: ["string", "null"]
          description: Tag associated with this session
        messages:
          type: array
          description: Session messages. Only present when includeMessages=true
          items:
            $ref: '#/components/schemas/ChatMessage'
    ChatExport:
      type: object
      description: Chat export data for a single contact
      properties:
        contactId:
          type: string
          description: Contact identifier
        contactName:
          type: string
          description: Contact full name
        phoneNumber:
          type: string
          description: Contact phone number
        email:
          type: string
          description: Contact email address
        messageCount:
          type: integer
          description: Number of messages in the export
        chatExport:
          type: string
          description: Full chat export as formatted text
    OtpResponse:
      type: object
      description: OTP generation response
      properties:
        success:
          type: boolean
          examples: [true]
        current_otp:
          type: string
          description: Current 6-digit TOTP code
          examples: ["123456"]
        expires_in:
          type: integer
          description: Seconds until the current code expires (0-30)
          examples: [15]
  parameters:
    ApiKeyParam:
      name: apiKey
      in: query
      description: API key for authentication. At least one of apiKey or userId is required for protected endpoints.
      required: false
      schema:
        type: string
      example: "your_api_key"
    UserIdParam:
      name: userId
      in: query
      description: User ID for authentication. Alternative to apiKey. At least one of apiKey or userId is required for protected endpoints.
      required: false
      schema:
        type: string
      example: "your_user_id"

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: query
      name: apiKey
      description: Your DM Champ API key. Obtain from Settings → API Keys.
    UserIdAuth:
      type: apiKey
      in: query
      name: userId
      description: Your DM Champ User ID. Alternative to apiKey authentication. Cannot be used for agency-only endpoints.

paths:
  /health:
    get:
      summary: Health Check
      description: Check if the server is running
      tags:
        - System
      responses:
        '200':
          description: Server is healthy
          content:
            text/plain:
              schema:
                type: string
                examples: ["The server is running healthy! 🥳"]

  /contacts:
    post:
      summary: Create New Contact
      description: |
        Create a new contact with phone number and optional information.
        
        **New Feature**: You can now optionally add the contact to one or more lists upon creation.
        
        **List Assignment Options**:
        - Use `listId` for adding to a single list (simpler for single list case)
        - Use `listIds` for adding to multiple lists (array format)
        - If both are provided, `listIds` takes precedence
        
        The API will:
        - Validate that all lists exist
        - Verify you have permission to add contacts to each list
        - Create the contact and add it to the specified lists atomically
        
        If any list validation fails, the contact will not be created.
      tags:
        - Contacts
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Contact'
            examples:
              create_contact:
                summary: Create contact without lists
                value:
                  phoneNumber: "+1234567890"
                  firstName: "John"
                  lastName: "Doe"
                  email: "john@example.com"
                  is_bot_active: true
              create_contact_single_list:
                summary: Create contact with single list (using listId)
                value:
                  phoneNumber: "+1234567890"
                  firstName: "Alice"
                  lastName: "Johnson"
                  email: "alice@example.com"
                  is_bot_active: true
                  listId: "list123"
              create_contact_multiple_lists:
                summary: Create contact with multiple lists (using listIds)
                value:
                  phoneNumber: "+1234567890"
                  firstName: "Jane"
                  lastName: "Smith"
                  email: "jane@example.com"
                  is_bot_active: true
                  listIds: ["list123", "list456"]
      responses:
        '200':
          description: Contact created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        examples: ["Successfully created new contact"]
                      contactId:
                        type: string
                        examples: ["abc123"]
                      listsAdded:
                        type: array
                        items:
                          type: string
                        description: List IDs the contact was added to
                        examples: [["list123", "list456"]]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Invalid phone number"]
                  error_code:
                    type: integer
                    examples: [400]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["You don't have permission to add contacts to list: list123"]
                  error_code:
                    type: integer
                    examples: [403]
        '404':
          description: List not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["List not found: list123"]
                  error_code:
                    type: integer
                    examples: [404]
        '409':
          description: Contact already exists
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["A contact with this phone number already exists"]
                  error_code:
                    type: integer
                    examples: [409]
    get:
      summary: Get Contact
      description: Get a contact by phone number or email
      tags:
        - Contacts
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: phoneNumber
          in: query
          required: false
          schema:
            type: string
          description: Contact's phone number (must include valid country code)
          example: "+1234567890"
        - name: email
          in: query
          required: false
          schema:
            type: string
            format: email
          description: Contact's email address
          example: "john@example.com"
      responses:
        '200':
          description: Contact found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  contactId:
                    type: string
                    examples: ["abc123"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                oneOf:
                  - type: string
                    examples: ["Missing required parameters. Please provide a phone number or email."]
                  - type: object
                    properties:
                      error:
                        type: string
                        examples: ["Invalid phone number. Ensure it includes a valid country code and is in the correct format."]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: string
                examples: ["Invalid API key"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Contact not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Error occurred while fetching contact"]

  /contacts/{contactId}:
    put:
      summary: Update Contact
      description: Update an existing contact's information
      tags:
        - Contacts
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: ID of the contact to update
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                firstName:
                  type: string
                  description: Contact's first name
                lastName:
                  type: string
                  description: Contact's last name
                email:
                  type: string
                  description: Contact's email address
                  format: email
                is_bot_active:
                  type: boolean
                  description: Whether the bot is active for this contact
                do_not_disturb:
                  type: boolean
                  description: Whether the contact should not be disturbed
            examples:
              update_contact:
                value:
                  firstName: "John"
                  lastName: "Doe"
                  email: "john@example.com"
                  is_bot_active: false
                  do_not_disturb: true
      responses:
        '200':
          description: Contact updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  message:
                    type: string
                    examples: ["Contact updated successfully"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["No fields to update"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: string
                examples: ["Invalid API key"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Contact not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Internal Server Error"]
                  error_code:
                    type: integer
                    examples: [500]

  /tags:
    post:
      summary: Tag a Contact
      description: >
        Assign a tag to a contact. If the tag does not exist yet, it is created automatically.
        If the tag already exists, the contact is added to it.
        Identify the contact by phone number or email.
      tags:
        - Tags
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: tag
          in: query
          required: true
          schema:
            type: string
          description: The tag name to assign (created automatically if it does not exist)
        - name: phoneNumber
          in: query
          required: false
          schema:
            type: string
          description: Phone number of the contact (include country code, e.g. +15551234567). Required if email is not provided.
        - name: email
          in: query
          required: false
          schema:
            type: string
          description: Email address of the contact. Required if phoneNumber is not provided.
      responses:
        '201':
          description: Tag assigned to contact successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Tag'
        '400':
          description: Missing required parameters (tag and either phoneNumber or email)
        '401':
          description: Invalid API key
        '402':
          description: Insufficient credits
        '404':
          description: Contact not found
    get:
      summary: Get All Tags
      description: Retrieve all tags
      tags:
        - Tags
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      responses:
        '200':
          description: List of tags
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tag'

  /tags/{name}:
    get:
      summary: Get Tag by Name
      description: Retrieve a specific tag by name
      tags:
        - Tags
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Tag found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '404':
          description: Tag not found

  /contacts/lists:
    post:
      summary: Add Contact To List
      description: Add a specific contact to a list
      tags:
        - Contacts
        - Lists
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - contactId
                - listId
              properties:
                contactId:
                  type: string
                  description: ID of the contact to add to the list
                listId:
                  type: string
                  description: ID of the list to add the contact to
            examples:
              add_contact_to_list:
                value:
                  contactId: "contact123"
                  listId: "list456"
      responses:
        '200':
          description: Contact added to list successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  message:
                    type: string
                    examples: ["Contact successfully added to list My List"]
        '400':
          description: Missing required parameters
          content:
            application/json:
              schema:
                type: string
                examples: ["Missing required parameters"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: string
                examples: ["Invalid API key"]
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                type: string
                examples: ["You don't have permission to add this contact to a list"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Error occurred while adding contact to list."]
    delete:
      summary: Remove Contact From List
      description: Remove a specific contact from a list
      tags:
        - Contacts
        - Lists
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: contactId
          in: query
          required: true
          schema:
            type: string
          description: ID of the contact to remove from the list
          example: "contact123"
        - name: listId
          in: query
          required: true
          schema:
            type: string
          description: ID of the list to remove the contact from
          example: "list456"
      responses:
        '200':
          description: Contact removed from list successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  message:
                    type: string
                    examples: ["Contact successfully removed from list My List"]
        '400':
          description: Missing required parameters
          content:
            application/json:
              schema:
                type: string
                examples: ["Missing required parameters"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: string
                examples: ["Invalid API key"]
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                type: string
                examples: ["You don't have permission to remove this contact from a list"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Error occurred while adding contact to list."]

  /contacts/tags/{contactId}:
    get:
      summary: Get Contact Tags
      description: Get all tags associated with a contact
      tags:
        - Contacts
        - Tags
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: ID of the contact to get tags for
      responses:
        '200':
          description: List of contact tags
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tag'
        '400':
          description: Missing contact ID
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Missing required parameters. Please provide a contactId"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Contact not found"]

  /contacts/messages/{contactId}:
    get:
      summary: Get Contact Messages
      description: Retrieve messages for a specific contact
      tags:
        - Contacts
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: ID of the contact to get messages for
      responses:
        '200':
          description: List of messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Message'
        '400':
          description: Missing contact ID
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Missing required parameters. Please provide a contactId"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Contact not found"]

  /summaries:
    post:
      summary: Generate Chat Summary
      description: Generate a chat summary for a specific contact based on their conversation history
      tags:
        - Summaries
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                phoneNumber:
                  type: string
                  description: Contact's phone number (must include valid country code)
                email:
                  type: string
                  format: email
                  description: Contact's email address
              oneOf:
                - required: [phoneNumber]
                - required: [email]
            examples:
              withPhoneNumber:
                value:
                  phoneNumber: "+1234567890"
              withEmail:
                value:
                  email: "contact@example.com"
      responses:
        '200':
          description: Summary generation initiated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  message:
                    type: string
                    examples: ["Chat summary generated successfully"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: string
                examples: ["Missing required parameters. Please provide a phone number or email."]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: string
                examples: ["Invalid API key"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: string
                examples: ["Contact not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Internal Server Error"]

  /scripts/chat-widget.js:
    get:
      summary: Get Chat Widget Script
      description: Returns the JavaScript code needed to embed the chat widget on a website
      tags:
        - Chat Widget
      responses:
        '200':
          description: JavaScript code for chat widget
          content:
            application/javascript:
              schema:
                type: string
                description: JavaScript code

  /chat-widget-config:
    get:
      summary: Get Chat Widget Configuration
      description: Returns the configuration for the chat widget
      tags:
        - Chat Widget
      parameters:
        - name: configId
          in: query
          required: true
          schema:
            type: string
          description: ID of the chat widget configuration
      responses:
        '200':
          description: Chat widget configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: object
                    description: Widget configuration data
        '404':
          description: Configuration not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Configuration not found"]

  /chat-widget/{configId}:
    get:
      summary: Get Chat Widget
      description: Returns the HTML for the chat widget with the specified configuration
      tags:
        - Chat Widget
      parameters:
        - name: configId
          in: path
          required: true
          schema:
            type: string
          description: ID of the chat widget configuration
      responses:
        '200':
          description: HTML content for the chat widget
          content:
            text/html:
              schema:
                type: string
                description: HTML content
        '404':
          description: Widget not found
          content:
            text/plain:
              schema:
                type: string
                examples: ["Widget not found"]

  /chat-widget/{configId}/{fromId}:
    get:
      summary: Get Chat Widget with From ID
      description: Returns the HTML for the chat widget with the specified configuration and from ID
      tags:
        - Chat Widget
      parameters:
        - name: configId
          in: path
          required: true
          schema:
            type: string
          description: ID of the chat widget configuration
        - name: fromId
          in: path
          required: true
          schema:
            type: string
          description: ID of the source (for tracking purposes)
      responses:
        '200':
          description: HTML content for the chat widget
          content:
            text/html:
              schema:
                type: string
                description: HTML content
        '404':
          description: Widget not found
          content:
            text/plain:
              schema:
                type: string
                examples: ["Widget not found"]

  /incoming-chat-widget-message:
    post:
      summary: Handle Incoming Chat Widget Message
      description: Processes an incoming message from the chat widget and publishes it to a PubSub topic
      tags:
        - Chat Widget
        - Messages
      parameters:
        - name: userId
          in: query
          required: true
          schema:
            type: string
          description: ID of the user/owner to whom the chat widget belongs
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
                - messageType
              properties:
                customData:
                  type: object
                  required:
                    - body
                  properties:
                    messageSid:
                      type: string
                      description: Unique identifier for the message
                    id:
                      type: string
                      description: Alternative unique identifier for the message
                    fromId:
                      type: string
                      description: ID of the sender (visitor)
                    toId:
                      type: string
                      description: ID of the chat widget
                    body:
                      type: string
                      description: Message content
                    status:
                      type: string
                      description: Message status
                      default: "received"
                    mediaUrl:
                      type: ["string", "null"]
                      description: URL to media content (if any)
                    mediaContentType:
                      type: ["string", "null"]
                      description: MIME type of media content
                messageType:
                  type: string
                  description: Type of message (e.g., text, reaction)
                  default: "text"
            examples:
              textMessage:
                summary: Simple text message
                value: {
                  "customData": {
                    "messageSid": "msg123",
                    "fromId": "visitor123",
                    "toId": "widget456",
                    "body": "Hello from chat widget",
                    "status": "received"
                  },
                  "messageType": "text"
                }
              mediaMessage:
                summary: Message with media
                value: {
                  "customData": {
                    "messageSid": "msg123",
                    "fromId": "visitor123",
                    "toId": "widget456",
                    "body": "Check out this image",
                    "status": "received",
                    "mediaUrl": "https://example.com/image.jpg",
                    "mediaContentType": "image/jpeg"
                  },
                  "messageType": "text"
                }
      responses:
        '200':
          description: Message processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    description: PubSub message ID
                    examples: ["1234567890"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Message body cannot be empty"]
                  details:
                    type: string
                    description: Additional error details
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Method not allowed"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Internal server error"]

  /chat-widget-messages:
    get:
      summary: Get Chat Widget Messages
      description: Retrieves messages for a specific chat widget conversation
      tags:
        - Chat Widget
        - Messages
      parameters:
        - name: fromId
          in: query
          required: true
          schema:
            type: string
          description: ID of the user/owner of the contact
        - name: toId
          in: query
          required: true
          schema:
            type: string
          description: ID of the chat widget that the contact is using
        - name: after
          in: query
          required: false
          schema:
            type: string
            format: int64
          description: Optional timestamp (in milliseconds) to fetch only messages after this time
      responses:
        '200':
          description: List of messages with bot typing status
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      type: object
                      properties:
                        messageSid:
                          type: string
                          description: Unique message identifier
                        body:
                          type: string
                          description: Message content
                        mediaUrl:
                          type: ["string", "null"]
                          description: URL to media content (if any)
                        mediaContentType:
                          type: ["string", "null"]
                          description: MIME type of media content
                        timestamp:
                          type: string
                          format: date-time
                          description: Message timestamp
                        type:
                          type: string
                          enum: ["text", "image"]
                          description: Message type
                        direction:
                          type: string
                          enum: ["inbound", "outbound", "outbound-api"]
                          description: Message direction (inbound = from user, outbound = from system)
                        status:
                          type: string
                          description: Message delivery status
                  isBotTyping:
                    type: boolean
                    description: Whether the bot is currently typing a response
                    examples: [false]
              examples:
                success:
                  value: {
                    "messages": [
                      {
                        "messageSid": "msg123abc",
                        "body": "Hello there, how can I help you?",
                        "mediaUrl": null,
                        "mediaContentType": null,
                        "timestamp": "2023-05-15T14:30:45.123Z",
                        "type": "text",
                        "direction": "outbound",
                        "status": "delivered"
                      },
                      {
                        "messageSid": "msg456def",
                        "body": "I have a question about your services",
                        "mediaUrl": null,
                        "mediaContentType": null,
                        "timestamp": "2023-05-15T14:30:15.456Z",
                        "type": "text",
                        "direction": "inbound",
                        "status": "delivered"
                      }
                    ],
                    "isBotTyping": false
                  }
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing required parameters"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Contact not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Internal server error"]
                  details:
                    type: string
                    description: Error details (only provided in development environment)

  /update-message-read-status:
    post:
      summary: Mark Chat Widget Messages as Read
      description: Updates the read status of all unread messages for a specific chat widget conversation
      tags:
        - Chat Widget
        - Messages
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - fromId
                - toId
              properties:
                fromId:
                  type: string
                  description: ID of the user/owner of the contact
                toId:
                  type: string
                  description: ID of the chat widget that the contact is using
            examples:
              basic:
                value:
                  fromId: "user123"
                  toId: "widget456"
      responses:
        '200':
          description: Messages marked as read successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
        '400':
          description: Missing required parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing required parameters"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Contact not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Internal server error"]
                  details:
                    type: string
                    description: Error details (only provided in development environment)

  /whatsapp-widget/{phone}:
    get:
      summary: Get WhatsApp Widget
      description: Returns the HTML for the WhatsApp widget for the specified phone number
      tags:
        - WhatsApp Widget
      parameters:
        - name: phone
          in: path
          required: true
          schema:
            type: string
          description: Phone number with country code
      responses:
        '200':
          description: HTML content for the WhatsApp widget
          content:
            text/html:
              schema:
                type: string
                description: HTML content

  /whatsapp-widget/{phone}/{message}:
    get:
      summary: Get WhatsApp Widget with Message
      description: Returns the HTML for the WhatsApp widget for the specified phone number and pre-filled message
      tags:
        - WhatsApp Widget
      parameters:
        - name: phone
          in: path
          required: true
          schema:
            type: string
          description: Phone number with country code
        - name: message
          in: path
          required: true
          schema:
            type: string
          description: Pre-filled message
      responses:
        '200':
          description: HTML content for the WhatsApp widget
          content:
            text/html:
              schema:
                type: string
                description: HTML content

  /whatsapp-templates/send:
    post:
      summary: Send WhatsApp Template to Contact
      description: |
        Send a WhatsApp template to a contact, even if the chat is closed. The API will also reopen the chat session.
        
        **Template Variable Processing:**
        Templates support advanced variable syntax with the following features:
        
        1. **Basic Variables**: {{first_name}}, {{email}}, {{company}}
        2. **Default Values**: {{field|Default Text}} - Shows default if field is empty
        3. **Text Transformations**: 
           - {{field|uppercase}} - Converts to UPPERCASE
           - {{field|lowercase}} - Converts to lowercase
           - {{field|capitalize}} - Capitalizes first letter of each word
        4. **Combined Syntax**: {{field|Default Value|transformation}}
           Example: {{company|Your Company|uppercase}}
        
        **Note:** WhatsApp templates require Meta approval. The system automatically converts named variables to numbered placeholders ({{1}}, {{2}}, etc.) for WhatsApp API compatibility.
      tags:
        - WhatsApp Templates
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - contactId
                - whatsappTemplateId
              properties:
                contactId:
                  type: string
                  description: ID of the contact to send the template to
                whatsappTemplateId:
                  type: string
                  description: ID of the WhatsApp template to send
            examples:
              withApiKey:
                summary: Send WhatsApp template using API key
                value: {
                  "contactId": "contact123",
                  "whatsappTemplateId": "template456"
                }
              withAdvancedTemplate:
                summary: Template with advanced variable syntax
                value: {
                  "contactId": "contact123",
                  "whatsappTemplateId": "template789"
                }
                description: |
                  Template body example: "Hi {{first_name|there}}, welcome to {{company|Our Store|uppercase}}!"
                  Result: "Hi John, welcome to OUR STORE!" (or defaults if fields are empty)
      responses:
        '200':
          description: Template sent successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: string
                    examples: ["WhatsApp template message sent successfully"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [400]
                  error:
                    type: string
        '403':
          description: Twilio credentials not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [403]
                  error:
                    type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [500]

  /whatsapp-templates:
    get:
      summary: Get WhatsApp Templates
      description: Retrieve all WhatsApp templates for the authenticated user
      tags:
        - WhatsApp Templates
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      responses:
        '200':
          description: Successfully retrieved WhatsApp templates
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Template ID
                        name:
                          type: string
                          description: Template name
                        status:
                          type: string
                          description: Template approval status
                        language:
                          type: string
                          description: Template language
                        category:
                          type: string
                          description: Template category
                        components:
                          type: array
                          description: Template components
                        createdAt:
                          type: string
                          format: date-time
                          description: Template creation timestamp
                        template:
                          type: object
                          description: Full template data
              examples:
                default:
                  summary: Example templates list
                  value: {
                    "success": true,
                    "data": [
                      {
                        "id": "template123",
                        "name": "welcome_message",
                        "status": "approved",
                        "language": "en_US",
                        "category": "MARKETING",
                        "createdAt": "2023-01-01T00:00:00.000Z"
                      },
                      {
                        "id": "template456",
                        "name": "appointment_reminder",
                        "status": "approved",
                        "language": "en_US",
                        "category": "UTILITY",
                        "createdAt": "2023-01-02T00:00:00.000Z"
                      }
                    ]
                  }
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [400]
                  error:
                    type: string
                    examples: ["Authentication failed. Please provide a valid API key."]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [500]
                  error:
                    type: string
                    examples: ["Error occurred while retrieving WhatsApp templates"]

  /whatsapp-web/send:
    post:
      summary: Send WhatsApp Message via WhatsApp Web
      description: |
        Send a WhatsApp message directly to a phone number via WhatsApp Web.

        **Key Features:**
        - Send to any phone number (E.164 format required, e.g., +1234567890)
        - Automatic contact creation if contact doesn't exist
        - Message chunking: Messages are automatically split by double newlines (\n\n) into separate WhatsApp messages
        - Natural typing simulation: Delays between chunks simulate human typing speed (500 chars/min)
        - Typing indicators: Shows "typing..." status during message sending
        - Credit-based: 1 credit per message (regardless of number of chunks)

        **Requirements:**
        - Active WhatsApp Web connection required
        - Phone number must be in E.164 format (e.g., +1234567890)
        - Sufficient credits (1 credit per message)

        **Message Chunking Example:**

        Input message: "Hey Sohaib! 👋\n\nGreat news about your feature request!\n\nI'll let you know when it's live!"

        Results in 3 separate WhatsApp messages:
        1. Hey Sohaib! 👋
        2. Great news about your feature request!
        3. I'll let you know when it's live!

        **Note:** This endpoint uses WhatsApp Web, not the Meta Business API, so no template approval is required.
      tags:
        - WhatsApp Web
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - phoneNumber
                - message
              properties:
                phoneNumber:
                  type: string
                  description: Recipient's phone number in E.164 format (e.g., +1234567890)
                  example: "+1234567890"
                  pattern: "^[+][1-9][0-9]{1,14}$"
                message:
                  type: string
                  description: Message text. Will be automatically split by double newlines into separate messages.
                  example: |-
                    Hey Sohaib! 👋

                    Great news about your feature request!

                    I'll let you know when it's live!
            examples:
              singleMessage:
                summary: Send single message
                value:
                  phoneNumber: "+1234567890"
                  message: "Hello! This is a test message."
              chunkedMessage:
                summary: Send chunked message (will split into 3 messages)
                value:
                  phoneNumber: "+1234567890"
                  message: |-
                    Hey there! 👋

                    Great news about your feature request!

                    I'll let you know when it's live!
      responses:
        '200':
          description: Message(s) sent successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  contactId:
                    type: string
                    description: ID of the contact (created or existing)
                    examples: ["contact123"]
                  messagesSent:
                    type: integer
                    description: Number of message chunks successfully sent
                    examples: [3]
                  totalChunks:
                    type: integer
                    description: Total number of chunks the message was split into
                    examples: [3]
                  messageIds:
                    type: array
                    items:
                      type: string
                    description: Array of message IDs for each sent chunk
                    examples: [["msg1", "msg2", "msg3"]]
                  creditsUsed:
                    type: integer
                    description: Total credits deducted (always 1 per message, regardless of chunks)
                    examples: [1]
        '400':
          description: Bad request - Invalid input
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                  code:
                    type: string
              examples:
                missingFields:
                  summary: Missing required fields
                  value: {
                    "success": false,
                    "error": "Missing required fields: phoneNumber and message are required",
                    "code": "MISSING_REQUIRED_FIELDS"
                  }
                invalidPhone:
                  summary: Invalid phone number format
                  value: {
                    "success": false,
                    "error": "Invalid phone number format. Must be E.164 format (e.g., +1234567890)",
                    "code": "INVALID_PHONE_FORMAT"
                  }
                contactDND:
                  summary: Contact on Do Not Disturb list
                  value: {
                    "success": false,
                    "error": "Contact is on Do Not Disturb list. Reason: Previous message failed",
                    "code": "CONTACT_DND"
                  }
        '401':
          description: Unauthorized - Invalid API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Invalid API key"]
                  code:
                    type: string
                    examples: ["INVALID_API_KEY"]
        '402':
          description: Payment required - Insufficient credits
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Insufficient credits. Required: 3, Available: 1"]
                  code:
                    type: string
                    examples: ["INSUFFICIENT_CREDITS"]
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                  code:
                    type: string
                    examples: ["INTERNAL_ERROR"]
              examples:
                noConnection:
                  summary: No WhatsApp Web connection
                  value: {
                    "success": false,
                    "error": "User has no WhatsApp Web connection",
                    "code": "SEND_FAILED"
                  }
                sendFailed:
                  summary: Message send failed
                  value: {
                    "success": false,
                    "error": "Failed to send message",
                    "code": "SEND_FAILED"
                  }

  /incoming-custom-channel-message:
    post:
      summary: Handle Custom Channel Message
      description: Handles incoming messages from custom channels. Campaign Assignment Priority - 1. Explicit campaignId (highest priority) 2. Keyword matching (existing logic) 3. Default campaign (fallback). Backwards Compatibility - All new fields are optional. Existing integrations continue to work unchanged.
      tags:
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
                - messageType
              properties:
                customData:
                  type: object
                  required:
                    - body
                  properties:
                    messageSid:
                      type: string
                      description: Unique identifier for the message
                    id:
                      type: string
                      description: Alternative unique identifier for the message
                    fromId:
                      type: string
                      description: ID of the sender
                    toId:
                      type: string
                      description: ID of the receiver
                    body:
                      type: string
                      description: Message content
                    status:
                      type: string
                      description: Message status
                      default: "received"
                    mediaUrl:
                      type: ["string", "null"]
                      description: URL to media content (if any)
                    mediaContentType:
                      type: ["string", "null"]
                      description: MIME type of media content
                    channel:
                      type: ["string", "null"]
                      description: Custom channel identifier
                    campaignId:
                      type: ["string", "null"]
                      description: Explicit campaign ID to assign contact to (highest priority)
                    firstName:
                      type: ["string", "null"]
                      description: Contact's first name (for contact creation/update)
                    lastName:
                      type: ["string", "null"]
                      description: Contact's last name (for contact creation/update)
                    email:
                      type: ["string", "null"]
                      description: Contact's email address (for contact creation/update)
                messageType:
                  type: string
                  description: Type of message (e.g., text, reaction)
                  default: "text"
            examples:
              textMessage:
                summary: Simple text message
                value: {
                  "customData": {
                    "messageSid": "msg123",
                    "fromId": "user123",
                    "toId": "business456",
                    "body": "Hello from custom channel",
                    "status": "received",
                    "channel": "custom"
                  },
                  "messageType": "text"
                }
              messageWithCampaign:
                summary: Message with explicit campaign assignment
                value: {
                  "customData": {
                    "messageSid": "msg123",
                    "fromId": "user123",
                    "toId": "business456",
                    "body": "Hello from custom channel",
                    "status": "received",
                    "channel": "telegram",
                    "campaignId": "camp_456",
                    "firstName": "John",
                    "lastName": "Doe",
                    "email": "john@example.com"
                  },
                  "messageType": "text"
                }
              mediaMessage:
                summary: Message with media
                value: {
                  "customData": {
                    "messageSid": "msg123",
                    "fromId": "user123",
                    "toId": "business456",
                    "body": "Check out this image",
                    "status": "received",
                    "mediaUrl": "https://example.com/image.jpg",
                    "mediaContentType": "image/jpeg",
                    "channel": "custom"
                  },
                  "messageType": "text"
                }
      responses:
        '200':
          description: Message processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    examples: ["1234567890"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Message body cannot be empty"]
                  details:
                    type: string
                    description: Additional error details
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing API key or userId in query parameters"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Internal server error"]

  /incoming_custom_channel_message:
    post:
      summary: Handle Custom Channel Message (Alternative)
      description: Alternative endpoint for handling incoming messages from custom channels. Same functionality as /incoming-custom-channel-message. Campaign Assignment Priority - 1. Explicit campaignId (highest priority) 2. Keyword matching (existing logic) 3. Default campaign (fallback). Backwards Compatibility - All new fields are optional. Existing integrations continue to work unchanged.
      tags:
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
                - messageType
              properties:
                customData:
                  type: object
                  required:
                    - body
                  properties:
                    messageSid:
                      type: string
                      description: Unique identifier for the message
                    id:
                      type: string
                      description: Alternative unique identifier for the message
                    fromId:
                      type: string
                      description: ID of the sender
                    toId:
                      type: string
                      description: ID of the receiver
                    body:
                      type: string
                      description: Message content
                    status:
                      type: string
                      description: Message status
                      default: "received"
                    mediaUrl:
                      type: ["string", "null"]
                      description: URL to media content (if any)
                    mediaContentType:
                      type: ["string", "null"]
                      description: MIME type of media content
                    channel:
                      type: ["string", "null"]
                      description: Custom channel identifier
                    campaignId:
                      type: ["string", "null"]
                      description: Explicit campaign ID to assign contact to (highest priority)
                    firstName:
                      type: ["string", "null"]
                      description: Contact's first name (for contact creation/update)
                    lastName:
                      type: ["string", "null"]
                      description: Contact's last name (for contact creation/update)
                    email:
                      type: ["string", "null"]
                      description: Contact's email address (for contact creation/update)
                messageType:
                  type: string
                  description: Type of message (e.g., text, reaction)
                  default: "text"
            examples:
              textMessage:
                summary: Simple text message
                value: {
                  "customData": {
                    "messageSid": "msg123",
                    "fromId": "user123",
                    "toId": "business456",
                    "body": "Hello from custom channel",
                    "status": "received",
                    "channel": "custom"
                  },
                  "messageType": "text"
                }
      responses:
        '200':
          description: Message processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    examples: ["1234567890"]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Message body cannot be empty"]
                  details:
                    type: string
                    description: Additional error details
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing API key or userId in query parameters"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Internal server error"]

  /send-custom-channel-message:
    post:
      summary: Send Custom Channel Message
      description: Send an outbound message to a custom channel contact. Creates contact if it doesn't exist.
      tags:
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
              properties:
                customData:
                  type: object
                  required:
                    - fromId
                    - customChannel
                    - body
                  properties:
                    fromId:
                      type: string
                      description: External ID of the contact
                    customChannel:
                      type: string
                      description: Custom channel identifier
                    body:
                      type: string
                      description: Message content to send
                    campaignId:
                      type: ["string", "null"]
                      description: Campaign ID to assign contact to (if creating new contact)
                    firstName:
                      type: ["string", "null"]
                      description: Contact's first name (for contact creation/update)
                    lastName:
                      type: ["string", "null"]
                      description: Contact's last name (for contact creation/update)
                    email:
                      type: ["string", "null"]
                      description: Contact's email address (for contact creation/update)
            examples:
              simpleOutbound:
                summary: Simple outbound message
                value: {
                  "customData": {
                    "fromId": "user_123",
                    "customChannel": "telegram",
                    "body": "Hello! How can I help you today?"
                  }
                }
              outboundWithCampaign:
                summary: Outbound message with campaign assignment
                value: {
                  "customData": {
                    "fromId": "user_123",
                    "customChannel": "telegram",
                    "body": "Hello! How can I help you today?",
                    "campaignId": "camp_456",
                    "firstName": "Jane",
                    "lastName": "Smith",
                    "email": "jane@example.com"
                  }
                }
      responses:
        '200':
          description: Message sent successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    description: ID of the created message
                  contactId:
                    type: string
                    description: ID of the contact
                  message:
                    type: string
                    examples: ["Message sent successfully"]
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing required fields: fromId, customChannel, and body are required"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Internal server error"]
                  message:
                    type: string
                    description: Detailed error message

  /send_custom_channel_message:
    post:
      summary: Send Custom Channel Message (Alternative)
      description: Alternative endpoint for sending outbound messages to custom channel contacts. Same functionality as /send-custom-channel-message. Creates contact if it doesn't exist.
      tags:
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
              properties:
                customData:
                  type: object
                  required:
                    - fromId
                    - customChannel
                    - body
                  properties:
                    fromId:
                      type: string
                      description: External ID of the contact
                    customChannel:
                      type: string
                      description: Custom channel identifier
                    body:
                      type: string
                      description: Message content to send
                    campaignId:
                      type: ["string", "null"]
                      description: Campaign ID to assign contact to (if creating new contact)
                    firstName:
                      type: ["string", "null"]
                      description: Contact's first name (for contact creation/update)
                    lastName:
                      type: ["string", "null"]
                      description: Contact's last name (for contact creation/update)
                    email:
                      type: ["string", "null"]
                      description: Contact's email address (for contact creation/update)
            examples:
              simpleOutbound:
                summary: Simple outbound message
                value: {
                  "customData": {
                    "fromId": "user_123",
                    "customChannel": "telegram",
                    "body": "Hello! How can I help you today?"
                  }
                }
      responses:
        '200':
          description: Message sent successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    description: ID of the created message
                  contactId:
                    type: string
                    description: ID of the contact
                  message:
                    type: string
                    examples: ["Message sent successfully"]
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing required fields: fromId, customChannel, and body are required"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Internal server error"]
                  message:
                    type: string
                    description: Detailed error message

  /sync-custom-channel-message:
    post:
      summary: Sync Custom Channel Message
      description: |
        Syncs an externally-sent message (e.g., from GoHighLevel) back into DM Champ without re-sending it via the custom channel webhook.

        Use cases:
        1. A user manually sends a message from GHL — the AI gets context of what was sent.
        2. A GHL workflow sends an automated outbound message to a lead — the AI knows what was said before the lead ever replies.

        The message is stored with synced_from_history: true and sent_manually: true, which prevents the outbound trigger from re-sending it to the webhook.

        If the contact doesn't exist yet and customChannel is provided, the contact is auto-created.
        Optionally pauses the AI agent on the contact (default: true).
      tags:
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
              properties:
                customData:
                  type: object
                  required:
                    - fromId
                    - body
                  properties:
                    fromId:
                      type: string
                      description: External contact identifier (e.g., GHL contact ID)
                    body:
                      type: string
                      description: Message body text
                    customChannel:
                      type: string
                      description: Custom channel name (e.g., "GoHighLevel"). Required if contact doesn't exist yet (enables auto-creation).
                    messageSid:
                      type: string
                      description: External message ID for deduplication. If a message with this ID already exists, sync is skipped.
                    pauseAi:
                      type: boolean
                      description: Whether to pause the AI agent on this contact. Defaults to true.
                      default: true
                    mediaUrl:
                      type: string
                      description: Optional media URL
                    mediaContentType:
                      type: string
                      description: Optional media content type (e.g., "image/jpeg")
                    timestamp:
                      type: integer
                      description: Optional timestamp in Unix milliseconds. Defaults to current time if not provided.
                    firstName:
                      type: string
                      description: Contact's first name (used for contact creation if contact doesn't exist yet)
                    lastName:
                      type: string
                      description: Contact's last name (used for contact creation if contact doesn't exist yet)
                    email:
                      type: string
                      description: Contact's email address (used for contact creation if contact doesn't exist yet)
                    campaignId:
                      type: string
                      description: Campaign ID to assign (used during contact creation if contact doesn't exist yet)
            examples:
              syncFromGHL:
                summary: Sync a message sent from GoHighLevel
                value: {
                  "customData": {
                    "fromId": "ghl-contact-id-123",
                    "body": "Hey, I just replied from GHL!",
                    "customChannel": "GoHighLevel",
                    "messageSid": "ghl-msg-456",
                    "pauseAi": true
                  }
                }
              syncWithMedia:
                summary: Sync a message with media attachment
                value: {
                  "customData": {
                    "fromId": "ghl-contact-id-123",
                    "body": "Check out this image",
                    "customChannel": "GoHighLevel",
                    "mediaUrl": "https://example.com/image.jpg",
                    "mediaContentType": "image/jpeg"
                  }
                }
              syncAutomatedWorkflow:
                summary: Sync an automated GHL workflow message (keep AI active)
                value: {
                  "customData": {
                    "fromId": "ghl-contact-id-789",
                    "body": "Hi! Thanks for signing up. We will be in touch shortly.",
                    "customChannel": "GoHighLevel",
                    "pauseAi": false,
                    "timestamp": 1710331200000
                  }
                }
      responses:
        '200':
          description: Message synced successfully (or duplicate detected)
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    description: ID of the created (or existing duplicate) message
                  contactId:
                    type: string
                    description: ID of the contact
                  aiPaused:
                    type: boolean
                    description: Whether the AI was paused on this contact
                  contactCreated:
                    type: boolean
                    description: Whether a new contact was created
                  message:
                    type: string
                    examples: ["Message synced successfully"]
        '400':
          description: Bad request - missing required fields or API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing required fields: fromId and body are required"]
        '401':
          description: Unauthorized - invalid API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '404':
          description: Contact not found (and no customChannel provided for auto-creation)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Contact not found"]
                  message:
                    type: string
                    description: Detailed explanation with guidance
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Internal server error"]
                  message:
                    type: string
                    description: Detailed error message

  /sync_custom_channel_message:
    post:
      summary: Sync Custom Channel Message (Alternative)
      description: Alternative endpoint for syncing externally-sent messages. Same functionality as /sync-custom-channel-message.
      tags:
        - Messages
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customData
              properties:
                customData:
                  type: object
                  required:
                    - fromId
                    - body
                  properties:
                    fromId:
                      type: string
                      description: External contact identifier (e.g., GHL contact ID)
                    body:
                      type: string
                      description: Message body text
                    customChannel:
                      type: string
                      description: Custom channel name (e.g., "GoHighLevel"). Required if contact doesn't exist yet.
                    messageSid:
                      type: string
                      description: External message ID for deduplication
                    pauseAi:
                      type: boolean
                      description: Whether to pause the AI agent on this contact
                      default: true
                    mediaUrl:
                      type: string
                      description: Optional media URL
                    mediaContentType:
                      type: string
                      description: Optional media content type
                    timestamp:
                      type: integer
                      description: Optional timestamp in Unix milliseconds
                    firstName:
                      type: string
                      description: Contact's first name (for contact creation)
                    lastName:
                      type: string
                      description: Contact's last name (for contact creation)
                    email:
                      type: string
                      description: Contact's email (for contact creation)
                    campaignId:
                      type: string
                      description: Campaign ID (for contact creation)
            examples:
              syncFromGHL:
                summary: Sync a message sent from GoHighLevel
                value: {
                  "customData": {
                    "fromId": "ghl-contact-id-123",
                    "body": "Hey, I just replied from GHL!",
                    "customChannel": "GoHighLevel"
                  }
                }
      responses:
        '200':
          description: Message synced successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  messageId:
                    type: string
                    description: ID of the created message
                  contactId:
                    type: string
                    description: ID of the contact
                  aiPaused:
                    type: boolean
                    description: Whether the AI was paused
                  contactCreated:
                    type: boolean
                    description: Whether a new contact was created
                  message:
                    type: string
                    examples: ["Message synced successfully"]
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Missing required fields: fromId and body are required"]
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: ["Contact not found"]
                  message:
                    type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Internal server error"]
                  message:
                    type: string
                    description: Detailed error message

  /subaccounts:
    post:
      summary: Create SubAccount
      description: Create a new sub-account within your agency. Only available for agency and developer accounts.
      tags:
        - SubAccounts
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - first_name
                - last_name
                - usage_limits
              properties:
                email:
                  type: string
                  format: email
                  description: Sub-account user's email address
                first_name:
                  type: string
                  description: Sub-account user's first name
                last_name:
                  type: string
                  description: Sub-account user's last name
                business_name:
                  type: string
                  description: Business name for the sub-account
                description:
                  type: string
                  description: Business description
                address_line:
                  type: string
                  description: Business address line
                city:
                  type: string
                  description: Business city
                country:
                  type: string
                  description: Business country
                postal_code:
                  type: string
                  description: Business postal code
                state:
                  type: string
                  description: Business state
                time_zone_id:
                  type: string
                  description: Time zone identifier
                language:
                  type: string
                  description: Preferred language
                usage_limits:
                  type: object
                  required:
                    - monthly_credits
                  properties:
                    monthly_credits:
                      type: integer
                      minimum: 1
                      description: Total monthly credits allocated
                    monthly_credits_used:
                      type: integer
                      default: 0
                      description: Number of monthly credits used
                    credits:
                      type: integer
                      description: Initial credit balance (defaults to monthly_credits if not specified)
                    roll_over_to_next_month:
                      type: boolean
                      default: false
                      description: Whether unused credits roll over to next month
            examples:
              createSubAccount:
                value: {
                  "email": "subaccount@example.com",
                  "first_name": "John",
                  "last_name": "Doe",
                  "business_name": "Sample Business",
                  "description": "Business description",
                  "address_line": "123 Main St",
                  "city": "Sample City",
                  "country": "US",
                  "postal_code": "12345",
                  "state": "CA",
                  "time_zone_id": "America/Los_Angeles",
                  "language": "en",
                  "usage_limits": {
                    "monthly_credits": 1000,
                    "credits": 1000,
                    "roll_over_to_next_month": false
                  }
                }
      responses:
        '201':
          description: Sub-account created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: object
                    properties:
                      email:
                        type: string
                        format: email
                        description: Sub-account email
                      first_name:
                        type: string
                        description: First name
                      last_name:
                        type: string
                        description: Last name
                      role:
                        type: string
                        examples: ["Sub Account"]
                        description: User role
                      is_agency_sub_account:
                        type: boolean
                        examples: [true]
                        description: Indicates this is a sub-account
                      uid:
                        type: string
                        description: Firebase UID for the sub-account
                      created_time:
                        type: string
                        format: date-time
                        description: Account creation timestamp
                      temporary_password:
                        type: string
                        description: Temporary password for the sub-account user
                      usage_limits:
                        type: object
                        properties:
                          monthly_credits:
                            type: integer
                            description: Total monthly credits allocated
                          monthly_credits_used:
                            type: integer
                            description: Number of monthly credits used
                          last_reset_date:
                            type: string
                            format: date-time
                            description: Date when credits were last reset
                          credits:
                            type: integer
                            description: Current credit balance
                          roll_over_to_next_month:
                            type: boolean
                            description: Whether unused credits roll over to next month
        '400':
          description: Invalid input or insufficient credits or not an agency account
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [400]
                  error:
                    type: string
                    enum:
                      - "Missing required fields including usage limits"
                      - "Monthly credits must be greater than 0"
                      - "You are not an agency account"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [500]
                  error:
                    type: string
                    examples: ["An error occurred while creating the agency account"]
    get:
      summary: Get SubAccount
      description: Retrieve information about a specific sub-account. Only available for agency and developer accounts.
      tags:
        - SubAccounts
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: email
          in: query
          required: true
          schema:
            type: string
            format: email
          description: Email of the sub-account to retrieve
      responses:
        '200':
          description: Sub-account found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    $ref: '#/components/schemas/SubAccount'
        '400':
          description: Invalid request or not an agency account
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [400]
                  error:
                    type: string
                    examples: ["You are not an agency account"]
        '404':
          description: Sub-account not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [404]
                  error:
                    type: string
                    examples: ["Agency Sub Account not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [500]
                  error:
                    type: string

    delete:
      summary: Delete SubAccount
      description: Delete a specific sub-account. Only available for agency and developer accounts.
      tags:
        - SubAccounts
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: email
          in: query
          required: true
          schema:
            type: string
            format: email
          description: Email of the sub-account to delete
      responses:
        '200':
          description: Sub-account deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  message:
                    type: string
                    examples: ["Sub-account deleted successfully"]
        '400':
          description: Invalid request or not an agency account
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [400]
                  error:
                    type: string
                    examples: ["You are not an agency account"]
        '404':
          description: Sub-account not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [404]
                  error:
                    type: string
                    examples: ["Agency Sub Account not found"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    enum: [500]
                  error:
                    type: string
                    examples: ["Error occurred while deleting sub-account"]

  /users/lists:
    get:
      summary: Get User Lists
      description: Retrieve all lists associated with the authenticated user
      tags:
        - Users
        - Lists
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
      responses:
        '200':
          description: Lists retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  lists:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Unique identifier for the list
                          examples: ["list123"]
                        name:
                          type: string
                          description: Name of the list
                          examples: ["My Contacts"]
        '400':
          description: Missing API key
          content:
            application/json:
              schema:
                type: string
                examples: ["Missing required parameter: apiKey"]
        '401':
          description: Invalid API key
          content:
            application/json:
              schema:
                type: string
                examples: ["Invalid API key"]
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  message:
                    type: string
                    examples: ["Error occurred while retrieving user lists."]

  /chat-sessions/recent:
    get:
      summary: Get Recent Chat Sessions
      description: |
        Retrieve chat sessions from the past X hours across all contacts belonging to the authenticated user.
        Supports optional filtering by session status and message inclusion.
      tags:
        - Chat Sessions
      security:
        - ApiKeyAuth: []
        - UserIdAuth: []
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - $ref: '#/components/parameters/UserIdParam'
        - name: hours
          in: query
          required: true
          schema:
            type: integer
            minimum: 1
          description: Number of hours to look back for recent sessions
          example: 24
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [ChatSessionOpened, ChatSessionClosed]
          description: Filter sessions by status
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 100
            minimum: 1
          description: Maximum number of sessions to return
        - name: includeMessages
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: Whether to include messages in each session
      responses:
        '200':
          description: Recent chat sessions retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: object
                    properties:
                      hours_ago:
                        type: integer
                        description: Number of hours looked back
                        examples: [24]
                      total_sessions:
                        type: integer
                        description: Total number of sessions found
                        examples: [15]
                      sessions:
                        type: array
                        items:
                          $ref: '#/components/schemas/RecentChatSession'
        '400':
          description: Missing or invalid hours parameter
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [400]
                  error:
                    type: string
                    examples: ["Missing required parameter: hours"]
        '401':
          description: Authentication required or invalid credentials
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [401]
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [500]
                  error:
                    type: string

  /chat-sessions/{contactId}:
    get:
      summary: Get Chat Sessions for Contact
      description: |
        Retrieve all chat sessions for a specific contact. The contact must belong to the authenticated user.
      tags:
        - Chat Sessions
      security:
        - ApiKeyAuth: []
        - UserIdAuth: []
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - $ref: '#/components/parameters/UserIdParam'
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: Firestore document ID of the contact
          example: "abc123xyz"
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [ChatSessionOpened, ChatSessionClosed]
          description: Filter sessions by status
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 100
            minimum: 1
          description: Maximum number of sessions to return
        - name: includeMessages
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: Whether to include messages in each session
      responses:
        '200':
          description: Chat sessions retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: object
                    properties:
                      contact_id:
                        type: string
                        description: Contact identifier
                        examples: ["abc123xyz"]
                      contact_name:
                        type: string
                        description: Contact full name
                        examples: ["John Doe"]
                      total_sessions:
                        type: integer
                        description: Total number of sessions found
                        examples: [5]
                      sessions:
                        type: array
                        items:
                          $ref: '#/components/schemas/ChatSession'
        '400':
          description: Missing required parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [400]
                  error:
                    type: string
                    examples: ["Missing required parameter: contactId"]
        '401':
          description: Authentication required or invalid credentials
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [401]
                  error:
                    type: string
                    examples: ["Invalid API key"]
        '403':
          description: Contact does not belong to authenticated user
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [403]
                  error:
                    type: string
                    examples: ["You don't have permission to access this contact's chat sessions"]
        '404':
          description: Contact not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [404]
                  error:
                    type: string
                    examples: ["Contact not found"]

  /chat-exports/recent:
    get:
      summary: Get Recent Chat Exports
      description: |
        Export chat history for all contacts with recent activity within the specified number of hours.
        Supports JSON (default) and plain text file formats.

        Text format returns a downloadable .txt file attachment with all conversations combined.
        JSON format returns structured data with per-contact message exports.
      tags:
        - Chat Exports
      security:
        - ApiKeyAuth: []
        - UserIdAuth: []
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - $ref: '#/components/parameters/UserIdParam'
        - name: hours
          in: query
          required: true
          schema:
            type: integer
            minimum: 1
          description: Number of hours to look back for recent contact activity
          example: 24
        - name: format
          in: query
          required: false
          schema:
            type: string
            enum: [json, txt]
            default: json
          description: Output format. Use 'txt' to download a plain text file attachment.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 50
            minimum: 1
          description: Maximum number of contacts to export
        - name: filter
          in: query
          required: false
          schema:
            type: string
            enum: [all, media, tool_use, text]
            default: all
          description: |
            Filter messages by type:
            - all: Include all messages
            - media: Only messages with media attachments
            - tool_use: Only tool use / AI action messages
            - text: Only plain text messages (no media, no tool use)
      responses:
        '200':
          description: |
            Chat exports retrieved successfully.
            When format=txt, returns a downloadable text file.
            When format=json (default), returns structured JSON.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [true]
                  data:
                    type: object
                    properties:
                      hours_ago:
                        type: integer
                        description: Number of hours looked back
                        examples: [24]
                      total_contacts:
                        type: integer
                        description: Number of contacts in the export
                        examples: [10]
                      exports:
                        type: array
                        items:
                          $ref: '#/components/schemas/ChatExport'
            text/plain:
              schema:
                type: string
                description: Combined chat export as plain text file (when format=txt)
        '400':
          description: Missing or invalid hours parameter
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [400]
                  error:
                    type: string
                    examples: ["Missing required parameter: hours"]
        '401':
          description: Authentication required or invalid credentials
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [401]
                  error:
                    type: string
                    examples: ["Invalid API key"]

  /agencies/usage/export:
    get:
      summary: Export Agency Usage Report
      description: |
        Export credit usage data for all sub-accounts as a CSV file.

        **Requires Agency or Dev role.** This endpoint only accepts apiKey authentication (userId is not supported).

        Returns a downloadable CSV file with columns: Sub Account ID, Sub Account Email, Sub Account Name,
        Date, Amount, Reason, Campaign Name, Contact Name, Credit Type, Custom Keys Used, Description, AI Model.
      tags:
        - Agency
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ApiKeyParam'
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Start date for the report range (YYYY-MM-DD). Defaults to 30 days ago.
          example: "2024-01-01"
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date
          description: End date for the report range (YYYY-MM-DD). Defaults to today.
          example: "2024-01-31"
        - name: subAccountId
          in: query
          required: false
          schema:
            type: string
          description: Filter report to a specific sub-account ID. If omitted, all sub-accounts are included.
          example: "uid123abc"
      responses:
        '200':
          description: CSV usage report downloaded successfully
          content:
            text/csv:
              schema:
                type: string
                description: |
                  CSV file with agency usage data. Headers: Sub Account ID, Sub Account Email,
                  Sub Account Name, Date, Amount, Reason, Campaign Name, Contact Name,
                  Credit Type, Custom Keys Used, Description, AI Model
        '400':
          description: Invalid date format or date range
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [400]
                  error:
                    type: string
                    examples: ["Invalid date format. Use YYYY-MM-DD"]
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Missing required parameters. Please provide either an API key or user ID."]
        '403':
          description: Authenticated user is not an Agency or Dev account
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [403]
                  error:
                    type: string
                    examples: ["Access denied: Agency account required"]
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error_code:
                    type: integer
                    examples: [500]
                  error:
                    type: string
                    examples: ["Internal server error while generating usage report"]

  /otp/generate:
    get:
      summary: Generate OTP Code
      description: |
        Generate a current Time-Based One-Time Password (TOTP) code using the provided base32 secret.

        This endpoint is **public** — no authentication required.
        The caller must supply a valid base32-encoded TOTP secret (e.g., from an authenticator app setup).
        Returns the current 6-digit OTP and its remaining validity period.
      tags:
        - OTP
      parameters:
        - name: secret
          in: query
          required: true
          schema:
            type: string
          description: Base32-encoded TOTP secret key
          example: "JBSWY3DPEHPK3PXP"
      responses:
        '200':
          description: OTP generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OtpResponse'
        '400':
          description: Missing or invalid secret parameter
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Secret parameter is required"]
        '500':
          description: Error generating OTP
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    examples: [false]
                  error:
                    type: string
                    examples: ["Error generating OTP"]

  /otp:
    get:
      summary: OTP Generator Interface
      description: |
        Serves an interactive HTML page for generating and viewing TOTP codes in the browser.

        This endpoint is **public** — no authentication required.
        The caller must supply a valid base32-encoded TOTP secret via the secret query parameter.
        The page auto-refreshes the code every 30 seconds and shows a countdown timer.
      tags:
        - OTP
      parameters:
        - name: secret
          in: query
          required: true
          schema:
            type: string
          description: Base32-encoded TOTP secret key
          example: "JBSWY3DPEHPK3PXP"
      responses:
        '200':
          description: HTML OTP generator interface
          content:
            text/html:
              schema:
                type: string
                description: Interactive HTML page with auto-refreshing TOTP display
        '400':
          description: Missing secret parameter
          content:
            text/plain:
              schema:
                type: string
                examples: ["Secret parameter is required. Use ?secret=YOUR_SECRET in the URL."]

  /zenchef-widget:
    get:
      summary: Zenchef Booking Widget (query param)
      description: |
        Returns an embeddable JavaScript widget that adds a Zenchef restaurant booking button to a webpage.

        This endpoint is **public** — no authentication required.

        The widget displays a floating booking button that opens a Zenchef booking iframe modal,
        along with a WhatsApp contact button. Embed using a `<script src="...">` tag.

        Phone number can be provided via the phone query parameter.
      tags:
        - Widgets
      parameters:
        - name: phone
          in: query
          required: true
          schema:
            type: string
          description: WhatsApp phone number (digits only, no + or spaces)
          example: "33612345678"
        - name: restaurantId
          in: query
          required: true
          schema:
            type: string
          description: Zenchef restaurant ID for booking widget
          example: "123456"
        - name: position
          in: query
          required: false
          schema:
            type: string
            enum: [header, footer]
            default: footer
          description: Widget button position on the page
        - name: buttonText
          in: query
          required: false
          schema:
            type: string
            default: "Book a Table"
          description: Text to display on the booking button
        - name: primaryColor
          in: query
          required: false
          schema:
            type: string
            default: "#a47e1f"
          description: Primary color for the widget (hex code)
          example: "#a47e1f"
      responses:
        '200':
          description: JavaScript widget code
          content:
            application/javascript:
              schema:
                type: string
                description: Self-executing JavaScript function that injects the booking widget
        '400':
          description: Missing required parameters
          content:
            text/plain:
              schema:
                type: string
                examples: ["Restaurant ID is required (add ?restaurantId=YOUR_RESTAURANT_ID to the script URL)"]

  /zenchef-widget/{phone}:
    get:
      summary: Zenchef Booking Widget (phone in path)
      description: |
        Returns an embeddable JavaScript widget with phone number provided via the URL path.

        This endpoint is **public** — no authentication required.
        Embed using a `<script src="/v1/zenchef-widget/33612345678?restaurantId=123456">` tag.
      tags:
        - Widgets
      parameters:
        - name: phone
          in: path
          required: true
          schema:
            type: string
          description: WhatsApp phone number (digits only, no + or spaces)
          example: "33612345678"
        - name: restaurantId
          in: query
          required: true
          schema:
            type: string
          description: Zenchef restaurant ID for booking widget
          example: "123456"
        - name: position
          in: query
          required: false
          schema:
            type: string
            enum: [header, footer]
            default: footer
          description: Widget button position on the page
        - name: buttonText
          in: query
          required: false
          schema:
            type: string
            default: "Book a Table"
          description: Text to display on the booking button
        - name: primaryColor
          in: query
          required: false
          schema:
            type: string
            default: "#a47e1f"
          description: Primary color for the widget (hex code)
      responses:
        '200':
          description: JavaScript widget code
          content:
            application/javascript:
              schema:
                type: string
        '400':
          description: Missing required parameters
          content:
            text/plain:
              schema:
                type: string

  /zenchef-widget/{phone}/{message}:
    get:
      summary: Zenchef Booking Widget (phone + pre-filled message)
      description: |
        Returns an embeddable JavaScript widget with phone number and pre-filled WhatsApp message in the URL path.

        This endpoint is **public** — no authentication required.
        The message path segment is passed as a pre-filled WhatsApp message when the user clicks the WhatsApp button.
      tags:
        - Widgets
      parameters:
        - name: phone
          in: path
          required: true
          schema:
            type: string
          description: WhatsApp phone number (digits only, no + or spaces)
          example: "33612345678"
        - name: message
          in: path
          required: true
          schema:
            type: string
          description: Pre-filled WhatsApp message text
          example: "Hello, I'd like to make a reservation"
        - name: restaurantId
          in: query
          required: true
          schema:
            type: string
          description: Zenchef restaurant ID for booking widget
          example: "123456"
        - name: position
          in: query
          required: false
          schema:
            type: string
            enum: [header, footer]
            default: footer
        - name: buttonText
          in: query
          required: false
          schema:
            type: string
            default: "Book a Table"
        - name: primaryColor
          in: query
          required: false
          schema:
            type: string
            default: "#a47e1f"
      responses:
        '200':
          description: JavaScript widget code
          content:
            application/javascript:
              schema:
                type: string
        '400':
          description: Missing required parameters
          content:
            text/plain:
              schema:
                type: string

  /formitable-widget:
    get:
      summary: Formitable Booking Widget (query param)
      description: |
        Returns an embeddable JavaScript widget that adds a Formitable restaurant booking button to a webpage.

        This endpoint is **public** — no authentication required.

        The widget displays a floating booking button that opens a Formitable booking iframe modal,
        along with a WhatsApp contact button. Embed using a `<script src="...">` tag.

        Phone number can be provided via the phone query parameter.
      tags:
        - Widgets
      parameters:
        - name: phone
          in: query
          required: true
          schema:
            type: string
          description: WhatsApp phone number (digits only, no + or spaces)
          example: "33612345678"
        - name: restaurantId
          in: query
          required: true
          schema:
            type: string
          description: Formitable restaurant ID for booking widget
          example: "abc123"
        - name: position
          in: query
          required: false
          schema:
            type: string
            enum: [header, footer]
            default: footer
          description: Widget button position on the page
        - name: buttonText
          in: query
          required: false
          schema:
            type: string
            default: "Book a Table"
          description: Text to display on the booking button
        - name: primaryColor
          in: query
          required: false
          schema:
            type: string
            default: "#FF3800"
          description: Primary color for the widget (hex code). Default is Formitable orange.
          example: "#FF3800"
      responses:
        '200':
          description: JavaScript widget code
          content:
            application/javascript:
              schema:
                type: string
                description: Self-executing JavaScript function that injects the booking widget
        '400':
          description: Missing required parameters
          content:
            text/plain:
              schema:
                type: string
                examples: ["Restaurant ID is required (add ?restaurantId=YOUR_RESTAURANT_ID to the script URL)"]

  /formitable-widget/{phone}:
    get:
      summary: Formitable Booking Widget (phone in path)
      description: |
        Returns an embeddable JavaScript widget with phone number provided via the URL path.

        This endpoint is **public** — no authentication required.
        Embed using a `<script src="/v1/formitable-widget/33612345678?restaurantId=abc123">` tag.
      tags:
        - Widgets
      parameters:
        - name: phone
          in: path
          required: true
          schema:
            type: string
          description: WhatsApp phone number (digits only, no + or spaces)
          example: "33612345678"
        - name: restaurantId
          in: query
          required: true
          schema:
            type: string
          description: Formitable restaurant ID for booking widget
          example: "abc123"
        - name: position
          in: query
          required: false
          schema:
            type: string
            enum: [header, footer]
            default: footer
          description: Widget button position on the page
        - name: buttonText
          in: query
          required: false
          schema:
            type: string
            default: "Book a Table"
          description: Text to display on the booking button
        - name: primaryColor
          in: query
          required: false
          schema:
            type: string
            default: "#FF3800"
          description: Primary color for the widget (hex code)
      responses:
        '200':
          description: JavaScript widget code
          content:
            application/javascript:
              schema:
                type: string
        '400':
          description: Missing required parameters
          content:
            text/plain:
              schema:
                type: string

  /formitable-widget/{phone}/{message}:
    get:
      summary: Formitable Booking Widget (phone + pre-filled message)
      description: |
        Returns an embeddable JavaScript widget with phone number and pre-filled WhatsApp message in the URL path.

        This endpoint is **public** — no authentication required.
        The message path segment is passed as a pre-filled WhatsApp message when the user clicks the WhatsApp button.
      tags:
        - Widgets
      parameters:
        - name: phone
          in: path
          required: true
          schema:
            type: string
          description: WhatsApp phone number (digits only, no + or spaces)
          example: "33612345678"
        - name: message
          in: path
          required: true
          schema:
            type: string
          description: Pre-filled WhatsApp message text
          example: "Hello, I'd like to make a reservation"
        - name: restaurantId
          in: query
          required: true
          schema:
            type: string
          description: Formitable restaurant ID for booking widget
          example: "abc123"
        - name: position
          in: query
          required: false
          schema:
            type: string
            enum: [header, footer]
            default: footer
        - name: buttonText
          in: query
          required: false
          schema:
            type: string
            default: "Book a Table"
        - name: primaryColor
          in: query
          required: false
          schema:
            type: string
            default: "#FF3800"
      responses:
        '200':
          description: JavaScript widget code
          content:
            application/javascript:
              schema:
                type: string
        '400':
          description: Missing required parameters
          content:
            text/plain:
              schema:
                type: string

  /checkout:
    get:
      summary: Agency Public Checkout
      description: |
        Creates a Stripe Checkout session for unauthenticated users (prospects) to purchase
        credits and become sub-accounts of an agency.

        This endpoint is **public** — no authentication required. It is designed for shareable
        payment links that agencies can distribute to potential sub-accounts.

        **Flow**:
        1. Agency shares the checkout URL with query params `agencyId` and `tierIndex`
        2. Endpoint validates params, fetches agency pricing config
        3. Creates a Stripe Checkout session on the agency's own Stripe account (BYOK)
        4. Redirects (303) to the Stripe hosted checkout page
        5. Stripe collects the buyer's email and name
        6. On payment success, a Firebase Auth user and Firestore doc are created automatically
        7. A welcome email with a temporary password is sent to the buyer
      tags:
        - Agency
      parameters:
        - name: agencyId
          in: query
          required: true
          schema:
            type: string
          description: Firebase UID of the agency
          example: "fHjQASIIzfUcrvuAvaYWdDd65Bu2"
        - name: tierIndex
          in: query
          required: true
          schema:
            type: integer
            minimum: 0
          description: Zero-based index into the agency's credit pricing tiers
          example: 0
      responses:
        '303':
          description: Redirect to Stripe Checkout hosted page
        '400':
          description: Missing or invalid parameters
          content:
            text/plain:
              schema:
                type: string
        '404':
          description: Agency not found
          content:
            text/plain:
              schema:
                type: string
        '500':
          description: Internal server error
          content:
            text/plain:
              schema:
                type: string
