openapi: 3.1.0
info:
  title: Contact Button API
  version: 1.0.0-alpha.1
  description: |
    API-first management and public agent actions for Contact Buttons and button.contact pages.
    API v1 is the first public contract. Existing private legacy API and browser workflows remain supported.
  contact:
    name: Contact Button
    url: https://learn.contactbutton.com
servers:
  - url: https://app.contactbutton.com/v1
    description: Production
tags:
  - name: Onboarding
    description: Human-verified agent onboarding.
  - name: Workspaces
  - name: Team
  - name: Pages
  - name: Domains
  - name: Buttons
  - name: Forms
  - name: Surveys
  - name: Apps
  - name: Knowledge
  - name: Booking
  - name: Telephony
    description: Bring-your-own-Telnyx setup, call flows, phone numbers, and voice interactions.
  - name: Reviews
    description: Send consented review requests through workspace-owned email, SMS, and WhatsApp channels.
  - name: Commerce
  - name: Engagement inbox
  - name: Public actions
    description: Public, rate-limited actions that may require human confirmation.
security:
  - bearerAuth: []
paths:
  /signups:
    post:
      tags: [Onboarding]
      summary: Start agent signup
      operationId: startAgentSignup
      security: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentSignupInput'
      responses:
        '202':
          description: Verification code sent to the represented human.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataResponse'
        '422': {$ref: '#/components/responses/Problem'}
  /signups/{signup}/verify:
    post:
      tags: [Onboarding]
      summary: Verify agent signup
      operationId: verifyAgentSignup
      security: []
      parameters:
        - name: signup
          in: path
          required: true
          schema: {type: string, format: uuid}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [otp_code]
              properties:
                otp_code: {type: string, pattern: '^[0-9]{6}$'}
      responses:
        '200':
          description: Scoped bearer token and user identity.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/DataResponse'}
        '422': {$ref: '#/components/responses/Problem'}
  /app-catalog:
    get:
      tags: [Apps]
      summary: List available app types
      operationId: listAppCatalog
      security: []
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /pages/slugs/{slug}/availability:
    get:
      tags: [Pages]
      summary: Check button.contact slug availability
      operationId: checkPageSlug
      security: []
      parameters:
        - name: slug
          in: path
          required: true
          schema: {type: string}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces:
    get:
      tags: [Workspaces]
      summary: List workspaces
      operationId: listWorkspaces
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Workspaces]
      summary: Create a workspace
      operationId: createWorkspace
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WorkspaceInput'
                - type: object
                  required: [name]
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Workspaces]
      summary: Get a workspace
      operationId: getWorkspace
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '404': {$ref: '#/components/responses/Problem'}
    patch:
      tags: [Workspaces]
      summary: Update a workspace
      operationId: updateWorkspace
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/WorkspaceInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/settings:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Workspaces]
      summary: Get notification and agent policy settings
      operationId: getWorkspaceSettings
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Workspaces]
      summary: Update notification and agent policy settings
      operationId: updateWorkspaceSettings
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/WorkspaceSettingsInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/team:
    get:
      tags: [Team]
      summary: List team members and pending invitations
      operationId: listTeam
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/team/invitations:
    post:
      tags: [Team]
      summary: Invite a team member
      operationId: inviteTeamMember
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, role]
              properties:
                email: {type: string, format: email}
                role: {type: string, enum: [owner, editor]}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '403': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/team/invitations/{invitation}:
    delete:
      tags: [Team]
      summary: Revoke a pending invitation
      operationId: revokeTeamInvitation
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: invitation
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        '204': {description: Invitation revoked.}
  /workspaces/{workspace}/team/members/{member}:
    delete:
      tags: [Team]
      summary: Remove a team member
      operationId: removeTeamMember
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: member
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        '204': {description: Member removed.}
  /workspaces/{workspace}/pages:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Pages]
      summary: List pages
      operationId: listPages
      parameters:
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Pages]
      summary: Create a page
      operationId: createPage
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/PageInput'
                - type: object
                  required: [name, slug]
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/pages/{page}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/PageId'
    get:
      tags: [Pages]
      summary: Get a page
      operationId: getPage
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Pages]
      summary: Update a page
      operationId: updatePage
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PageInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/pages/{page}/publish:
    post:
      tags: [Pages]
      summary: Publish a page
      operationId: publishPage
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/PageId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/pages/{page}/unpublish:
    post:
      tags: [Pages]
      summary: Unpublish a page
      operationId: unpublishPage
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/PageId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/domains:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Domains]
      summary: List custom domains
      operationId: listDomains
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Domains]
      summary: Attach a custom domain
      operationId: createDomain
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [page_id, host]
              properties:
                page_id: {type: string, format: uuid}
                host: {type: string, example: contact.example.com}
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/domains/{domain}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/DomainId'
    get:
      tags: [Domains]
      summary: Get domain status
      operationId: getDomain
      responses:
        '200': {$ref: '#/components/responses/Data'}
    delete:
      tags: [Domains]
      summary: Detach a custom domain
      operationId: deleteDomain
      responses:
        '204': {description: Domain detached.}
  /workspaces/{workspace}/domains/{domain}/verify:
    post:
      tags: [Domains]
      summary: Verify domain DNS
      operationId: verifyDomain
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/DomainId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/buttons:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Buttons]
      summary: List buttons
      operationId: listButtons
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Buttons]
      summary: Create a button
      operationId: createButton
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/ButtonInput'
                - type: object
                  required: [title]
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/button-background-assets:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    post:
      tags: [Buttons]
      summary: Upload a button background image
      description: Stores a workspace-owned image and creates responsive WebP variants for button backgrounds.
      operationId: createButtonBackgroundAsset
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: JPEG, PNG, or WebP image up to 5 MB.
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/button-templates:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Buttons]
      summary: List versioned system button templates
      operationId: listButtonTemplates
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/button-templates/{template}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - name: template
        in: path
        required: true
        schema:
          type: string
          enum: [ecommerce, local-business, restaurant, omnichannel, social-links, click-to-call, contact-form, whatsapp-chat]
    post:
      tags: [Buttons]
      summary: Create a button and its ordered apps from a system template
      operationId: instantiateButtonTemplate
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ButtonTemplateInstantiation'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/buttons/{button}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/ButtonId'
    get:
      tags: [Buttons]
      summary: Get a button
      operationId: getButton
      responses:
        '200': {$ref: '#/components/responses/Data'}
    put:
      tags: [Buttons]
      summary: Update a button (PUT compatibility)
      description: Compatibility alias for PATCH. PATCH is preferred because updates are partial.
      operationId: putButton
      deprecated: true
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ButtonInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Buttons]
      summary: Update a button
      operationId: updateButton
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ButtonInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    delete:
      tags: [Buttons]
      summary: Delete a button
      operationId: deleteButton
      responses:
        '204': {description: Button soft-deleted.}
  /workspaces/{workspace}/forms:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Forms]
      summary: List forms
      operationId: listForms
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Forms]
      summary: Create a structured form
      operationId: createForm
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/FormInput'
                - type: object
                  required: [name, fields]
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/forms/{form}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/FormId'
    get:
      tags: [Forms]
      summary: Get a form
      operationId: getForm
      responses:
        '200': {$ref: '#/components/responses/Data'}
    put:
      tags: [Forms]
      summary: Update a form (PUT compatibility)
      description: Compatibility alias for PATCH. PATCH is preferred because updates are partial.
      operationId: putForm
      deprecated: true
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/FormInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Forms]
      summary: Update a form
      operationId: updateForm
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/FormInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    delete:
      tags: [Forms]
      summary: Delete an unused form
      operationId: deleteForm
      responses:
        '204': {description: Form deleted.}
        '409': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/surveys:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Surveys]
      summary: List surveys
      operationId: listSurveys
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 100, default: 25}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Surveys]
      summary: Create a survey
      operationId: createSurvey
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/SurveyInput'
                - type: object
                  required: [name, questions]
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/surveys/{survey}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - name: survey
        in: path
        required: true
        schema: {type: string, format: uuid}
    get:
      tags: [Surveys]
      summary: Get a survey and its aggregate results
      operationId: getSurvey
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Surveys]
      summary: Update, publish, pause, or archive a survey
      operationId: updateSurvey
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SurveyInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
    delete:
      tags: [Surveys]
      summary: Delete a survey
      operationId: deleteSurvey
      responses:
        '204': {description: Survey soft-deleted.}
  /workspaces/{workspace}/surveys/{survey}/responses:
    get:
      tags: [Surveys]
      summary: List recent survey responses
      operationId: listSurveyResponses
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: survey
          in: path
          required: true
          schema: {type: string, format: uuid}
        - $ref: '#/components/parameters/Cursor'
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 100, default: 25}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/surveys/{survey}/distribution-preview:
    post:
      tags: [Surveys]
      summary: Validate an SMS or WhatsApp survey delivery
      description: Returns channel readiness, consent or template requirements, the final message, and an attributed survey URL. This endpoint does not send a message.
      operationId: previewSurveyDistribution
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: survey
          in: path
          required: true
          schema: {type: string, format: uuid}
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SurveyDistributionInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/apps:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Apps]
      summary: List configured apps
      operationId: listApps
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Apps]
      summary: Create an app instance
      operationId: createApp
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/AppInput'
                - type: object
                  required: [catalog_slug, title, fields]
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/apps/{app}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/AppId'
    get:
      tags: [Apps]
      summary: Get an app instance
      operationId: getApp
      responses:
        '200': {$ref: '#/components/responses/Data'}
    put:
      tags: [Apps]
      summary: Update an app instance (PUT compatibility)
      description: Compatibility alias for PATCH. PATCH is preferred because updates are partial.
      operationId: putApp
      deprecated: true
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/AppInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Apps]
      summary: Update an app instance
      operationId: updateApp
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/AppInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    delete:
      tags: [Apps]
      summary: Delete an unused app instance
      operationId: deleteApp
      responses:
        '204': {description: App deleted.}
  /workspaces/{workspace}/booking-event-types:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Booking]
      summary: List booking event types
      operationId: listBookingEventTypes
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Booking]
      summary: Create a booking event type
      operationId: createBookingEventType
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/BookingEventTypeInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/telephony:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Telephony]
      summary: Get the workspace Telnyx connection and readiness
      operationId: getTelephonyConnection
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Telephony]
      summary: Connect Telnyx or update phone capabilities
      description: |
        Initial bring-your-own-carrier setup requires both the Telnyx API V2 key and the account Ed25519 public key.
        Later updates may omit either credential to keep the stored value. The API key is encrypted and never returned.
      operationId: updateTelephonyConnection
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/TelephonyConnectionInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/call-flows:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Telephony]
      summary: List call flows
      operationId: listCallFlows
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Telephony]
      summary: Create a call flow
      operationId: createCallFlow
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CallFlowInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/call-flows/{flow}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/CallFlowId'
    patch:
      tags: [Telephony]
      summary: Update a call flow
      operationId: updateCallFlow
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CallFlowInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
    delete:
      tags: [Telephony]
      summary: Delete an unused call flow
      description: Assigned numbers, Click-to-Call apps, and active browser calls must be moved first.
      operationId: deleteCallFlow
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204': {description: Call flow deleted.}
        '409': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/phone-numbers/search:
    post:
      tags: [Telephony]
      summary: Search Telnyx numbers and create short-lived price quotes
      description: Search does not purchase a number. Returned quote IDs expire after ten minutes.
      operationId: searchPhoneNumbers
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PhoneNumberSearchInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/phone-numbers/order:
    post:
      tags: [Telephony]
      summary: Purchase a quoted Telnyx number
      description: |
        This is a paid external action. Show the human the exact number, monthly price, upfront price, and currency from
        the unexpired quote and obtain explicit approval before sending confirm_price.
      operationId: orderPhoneNumber
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PhoneNumberOrderInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/phone-numbers/{number}/resync:
    post:
      tags: [Telephony]
      summary: Refresh a Telnyx number order and regulatory status
      operationId: resyncPhoneNumber
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/PhoneNumberId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/voice-interactions:
    get:
      tags: [Telephony]
      summary: List recent inbound calls and web voice messages
      operationId: listVoiceInteractions
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/voice-interactions/{type}/{interaction}/audio:
    get:
      tags: [Telephony]
      summary: Read authorized call or voice-message audio
      operationId: getVoiceInteractionAudio
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: type
          in: path
          required: true
          schema: {type: string, enum: [call, voice_message]}
        - $ref: '#/components/parameters/VoiceInteractionId'
      responses:
        '200':
          description: Private audio stream.
          content:
            audio/mpeg: {schema: {type: string, format: binary}}
            audio/wav: {schema: {type: string, format: binary}}
            application/octet-stream: {schema: {type: string, format: binary}}
        '404': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/support/stripe-connect:
    get:
      tags: [Commerce]
      summary: Get Stripe Connect readiness
      operationId: getSupportStripeConnect
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/support/stripe-connect/onboarding:
    post:
      tags: [Commerce]
      summary: Start hosted Stripe Connect onboarding
      operationId: startSupportStripeConnectOnboarding
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '403': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/support/stripe-connect/synchronize:
    post:
      tags: [Commerce]
      summary: Synchronize Stripe Connect readiness
      operationId: synchronizeSupportStripeConnect
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/support/apps/{app}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/AppId'
    get:
      tags: [Commerce]
      summary: Get Support app configuration
      operationId: getSupportApp
      responses:
        '200': {$ref: '#/components/responses/Data'}
    put:
      tags: [Commerce]
      summary: Configure and activate a Support app
      operationId: updateSupportApp
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SupportAppInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/support/apps/{app}/payments:
    get:
      tags: [Commerce]
      summary: List Support app payments and fee reconciliation
      operationId: listSupportPayments
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/AppId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/apps/{app}/support:
    get:
      tags: [Commerce]
      summary: Get public Support app configuration
      operationId: getPublicSupportApp
      security: []
      parameters:
        - $ref: '#/components/parameters/AppId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '403': {$ref: '#/components/responses/Problem'}
  /public/apps/{app}/support/payment-intents:
    post:
      tags: [Commerce]
      summary: Create a one-time Support payment intent
      operationId: createSupportPaymentIntent
      security: []
      parameters:
        - $ref: '#/components/parameters/AppId'
        - $ref: '#/components/parameters/RequiredIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SupportPaymentInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '403': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/knowledge-lists:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
    get:
      tags: [Knowledge]
      summary: List knowledge lists
      operationId: listKnowledgeLists
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Knowledge]
      summary: Create a knowledge list
      operationId: createKnowledgeList
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/KnowledgeListInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}:
    get:
      tags: [Knowledge]
      summary: Get a knowledge list
      operationId: getKnowledgeList
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/articles:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/ListId'
    get:
      tags: [Knowledge]
      summary: List articles
      operationId: listKnowledgeArticles
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Knowledge]
      summary: Create an article draft
      operationId: createKnowledgeArticle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/KnowledgeArticleInput'
                - type: object
                  required: [title, body_markdown]
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/articles/{article}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/ListId'
      - $ref: '#/components/parameters/ArticleId'
    get:
      tags: [Knowledge]
      summary: Get an article
      operationId: getKnowledgeArticle
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Knowledge]
      summary: Create a new current article version
      operationId: updateKnowledgeArticle
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/KnowledgeArticleInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/articles/{article}/publish:
    post:
      tags: [Knowledge]
      summary: Publish the current article version
      operationId: publishKnowledgeArticle
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
        - $ref: '#/components/parameters/ArticleId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/articles/{article}/versions:
    get:
      tags: [Knowledge]
      summary: List article versions
      operationId: listKnowledgeArticleVersions
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
        - $ref: '#/components/parameters/ArticleId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/articles/{article}/versions/{version}/restore:
    post:
      tags: [Knowledge]
      summary: Restore an article version as a new current version
      operationId: restoreKnowledgeArticleVersion
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
        - $ref: '#/components/parameters/ArticleId'
        - name: version
          in: path
          required: true
          schema: {type: integer}
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                publish: {type: boolean, default: false}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/sources:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/ListId'
    get:
      tags: [Knowledge]
      summary: List sync sources
      operationId: listKnowledgeSources
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Knowledge]
      summary: Create a sync source
      operationId: createKnowledgeSource
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/KnowledgeSourceInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/sources/{source}/preview:
    post:
      tags: [Knowledge]
      summary: Preview a source without saving articles
      operationId: previewKnowledgeSource
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
        - $ref: '#/components/parameters/SourceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/sources/{source}/sync:
    post:
      tags: [Knowledge]
      summary: Queue a source sync
      operationId: syncKnowledgeSource
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
        - $ref: '#/components/parameters/SourceId'
      responses:
        '202': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/knowledge-lists/{list}/sources/{source}/runs:
    get:
      tags: [Knowledge]
      summary: List source sync runs
      operationId: listKnowledgeSyncRuns
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ListId'
        - $ref: '#/components/parameters/SourceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/knowledge-lists/{list}:
    get:
      tags: [Knowledge]
      summary: Read a public knowledge list
      operationId: getPublicKnowledgeList
      security: []
      parameters:
        - $ref: '#/components/parameters/ListId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/knowledge-lists/{list}/articles/{article}:
    get:
      tags: [Knowledge]
      summary: Read a published article
      operationId: getPublicKnowledgeArticle
      security: []
      parameters:
        - $ref: '#/components/parameters/ListId'
        - name: article
          in: path
          required: true
          schema: {type: string, description: Article UUID or slug.}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/knowledge-lists/{list}/search:
    get:
      tags: [Knowledge]
      summary: Search published knowledge
      operationId: searchPublicKnowledge
      security: []
      parameters:
        - $ref: '#/components/parameters/ListId'
        - name: query
          in: query
          required: true
          schema: {type: string, minLength: 2, maxLength: 500}
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 20, default: 8}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/subscribers:
    get:
      tags: [Engagement inbox]
      summary: List newsletter subscribers
      operationId: listSubscribers
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/form-submissions:
    get:
      tags: [Engagement inbox]
      summary: List verified agent form submissions
      operationId: listAgentFormSubmissions
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/bookings:
    get:
      tags: [Engagement inbox]
      summary: List bookings
      operationId: listBookings
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/bookings/{booking}:
    patch:
      tags: [Engagement inbox]
      summary: Update booking status
      operationId: updateBookingStatus
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/BookingId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status: {type: string, enum: [confirmed, cancelled, completed, no_show]}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/callbacks:
    get:
      tags: [Engagement inbox]
      summary: List callback requests
      operationId: listCallbacks
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/callbacks/{callback}:
    patch:
      tags: [Engagement inbox]
      summary: Update callback status
      operationId: updateCallbackStatus
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/CallbackId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status: {type: string, enum: [requested, in_progress, completed, cancelled]}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/review-destinations:
    get:
      tags: [Reviews]
      summary: List configured public review-site destinations
      operationId: listReviewDestinations
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/review-requests:
    get:
      tags: [Reviews]
      summary: List email, SMS, and WhatsApp review requests
      operationId: listReviewRequests
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 50}}
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Reviews]
      summary: Queue a consented review request
      description: Email uses the workspace mail service, SMS uses the configured Telnyx sender, and WhatsApp uses the connected Meta account with an approved template.
      operationId: createReviewRequest
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ReviewRequestInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/conversations:
    get:
      tags: [Engagement inbox]
      summary: List conversations
      operationId: listConversations
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/conversations/{conversation}:
    get:
      tags: [Engagement inbox]
      summary: Read a conversation and its messages
      operationId: getConversation
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ConversationId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/conversations/{conversation}/messages:
    post:
      tags: [Engagement inbox]
      summary: Reply to a conversation as its owner
      operationId: replyToConversation
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ConversationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body: {type: string, maxLength: 10000}
      responses:
        '201': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/conversations/{conversation}/join:
    post:
      tags: [Engagement inbox]
      summary: Mark a human as joined
      operationId: joinConversation
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ConversationId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/conversations/{conversation}/close:
    post:
      tags: [Engagement inbox]
      summary: Close a conversation
      operationId: closeConversation
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ConversationId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/inbox-contacts:
    get:
      tags: [Engagement inbox]
      summary: Search canonical Contacts for inbox compose
      operationId: searchInboxContacts
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - {name: search, in: query, schema: {type: string, maxLength: 200}}
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 50, default: 20}}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/channel-connections:
    get:
      tags: [Engagement inbox]
      summary: List multichannel inbox connections
      operationId: listInboxChannelConnections
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/channel-connections/{connection}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/ChannelConnectionId'
    patch:
      tags: [Engagement inbox]
      summary: Update and resynchronize a channel connection
      operationId: updateInboxChannelConnection
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                selected_folder_id: {type: string, maxLength: 500}
                selected_folder_name: {type: string, maxLength: 255}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    delete:
      tags: [Engagement inbox]
      summary: Disconnect a channel while retaining inbox history
      operationId: disconnectInboxChannel
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204': {description: Channel disconnected.}
  /workspaces/{workspace}/channel-connections/{connection}/sync:
    post:
      tags: [Engagement inbox]
      summary: Queue channel reconciliation
      operationId: syncInboxChannelConnection
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ChannelConnectionId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/channel-connections/{connection}/templates:
    get:
      tags: [Engagement inbox]
      summary: List WhatsApp templates
      operationId: listInboxChannelTemplates
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ChannelConnectionId'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/channel-connections/{connection}/templates/sync:
    post:
      tags: [Engagement inbox]
      summary: Synchronize WhatsApp templates
      operationId: syncInboxChannelTemplates
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ChannelConnectionId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/inbox-threads:
    get:
      tags: [Engagement inbox]
      summary: List Contact-centric multichannel threads
      operationId: listInboxThreads
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/Cursor'
        - {name: view, in: query, schema: {type: string, enum: [inbox, unread, starred, closed], default: inbox}}
        - {name: channel, in: query, schema: {type: string, enum: [web_chat, email, whatsapp, form]}}
        - {name: search, in: query, schema: {type: string, maxLength: 200}}
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 30}}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    post:
      tags: [Engagement inbox]
      summary: Compose a new email or WhatsApp message
      operationId: composeInboxMessage
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/InboxMessageInput'
                - type: object
                  required: [contact_id, channel]
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/inbox-threads/{thread}:
    parameters:
      - $ref: '#/components/parameters/WorkspaceId'
      - $ref: '#/components/parameters/InboxThreadId'
    get:
      tags: [Engagement inbox]
      summary: Read a unified thread timeline
      operationId: getInboxThread
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 100}}
      responses:
        '200': {$ref: '#/components/responses/Data'}
    patch:
      tags: [Engagement inbox]
      summary: Update shared unread, starred, or open state
      operationId: updateInboxThread
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/InboxThreadStateInput'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/inbox-threads/{thread}/messages:
    post:
      tags: [Engagement inbox]
      summary: Reply through a thread's selected channel
      operationId: replyToInboxThread
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/InboxThreadId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/InboxMessageInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/inbox-threads/{thread}/messages/{message}/retry:
    post:
      tags: [Engagement inbox]
      summary: Retry a failed outbound message
      operationId: retryInboxMessage
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/InboxThreadId'
        - $ref: '#/components/parameters/InboxMessageId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /workspaces/{workspace}/inbox-attachments:
    post:
      tags: [Engagement inbox]
      summary: Upload a private inbox attachment
      operationId: uploadInboxAttachment
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: {type: string, format: binary}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /workspaces/{workspace}/inbox-attachments/{attachment}:
    get:
      tags: [Engagement inbox]
      summary: Download a tenant-authorized private attachment
      operationId: downloadInboxAttachment
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/InboxAttachmentId'
        - {name: inline, in: query, schema: {type: boolean, default: false}}
      responses:
        '200':
          description: Attachment content.
          content:
            application/octet-stream:
              schema: {type: string, format: binary}
  /public/pages/{page}/forms/{form}/submission-requests:
    post:
      tags: [Public actions]
      summary: Request a human-confirmed form submission
      description: Available only when the published form is compatible with programmatic submission. Captcha, attachment, and unsupported custom fields require browser handoff.
      operationId: requestAgentFormSubmission
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - $ref: '#/components/parameters/FormId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/AgentFormSubmissionRequest'}
      responses:
        '202': {$ref: '#/components/responses/PendingConfirmation'}
        '409': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
  /public/surveys/{survey}:
    get:
      tags: [Surveys]
      summary: Get a published survey
      operationId: getPublicSurvey
      security: []
      parameters:
        - name: survey
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '404': {$ref: '#/components/responses/Problem'}
  /public/surveys/{survey}/responses:
    post:
      tags: [Surveys]
      summary: Submit a survey response
      operationId: createSurveyResponse
      security: []
      parameters:
        - name: survey
          in: path
          required: true
          schema: {type: string, format: uuid}
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SurveyResponseInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /public/pages/{page}/subscription-requests:
    post:
      tags: [Public actions]
      summary: Request a newsletter subscription
      operationId: requestSubscription
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionRequest'}
      responses:
        '202': {$ref: '#/components/responses/PendingConfirmation'}
  /public/pages/{page}/confirmation-challenges/{challenge}/confirm:
    post:
      tags: [Public actions]
      summary: Confirm a requested action
      operationId: confirmPublicAction
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - name: challenge
          in: path
          required: true
          schema: {type: string, format: uuid}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code: {type: string, pattern: '^[0-9]{6}$'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
        '422': {$ref: '#/components/responses/Problem'}
  /public/pages/{page}/booking-event-types:
    get:
      tags: [Booking]
      summary: List public booking event types
      operationId: listPublicBookingEventTypes
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/pages/{page}/booking-event-types/{eventType}/availability:
    get:
      tags: [Booking]
      summary: List available booking slots
      operationId: listBookingAvailability
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - name: eventType
          in: path
          required: true
          schema: {type: string, description: Event type UUID or slug.}
        - name: from
          in: query
          required: true
          schema: {type: string, format: date}
        - name: to
          in: query
          required: true
          schema: {type: string, format: date}
        - name: timezone
          in: query
          required: true
          schema: {type: string, example: Europe/Amsterdam}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/pages/{page}/booking-event-types/{eventType}/bookings:
    post:
      tags: [Booking]
      summary: Request a booking
      operationId: requestBooking
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - name: eventType
          in: path
          required: true
          schema: {type: string}
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/BookingRequest'}
      responses:
        '202': {$ref: '#/components/responses/PendingConfirmation'}
        '409': {$ref: '#/components/responses/Problem'}
  /public/pages/{page}/callback-requests:
    post:
      tags: [Public actions]
      summary: Request a callback
      operationId: requestCallback
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CallbackRequest'}
      responses:
        '202': {$ref: '#/components/responses/PendingConfirmation'}
  /public/pages/{page}/conversations:
    post:
      tags: [Public actions]
      summary: Start a conversation
      operationId: startConversation
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ConversationInput'}
      responses:
        '201': {$ref: '#/components/responses/Data'}
        '403': {$ref: '#/components/responses/Problem'}
  /public/pages/{page}/conversations/{conversation}/messages:
    post:
      tags: [Public actions]
      summary: Add a conversation message
      operationId: addConversationMessage
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - $ref: '#/components/parameters/ConversationId'
        - $ref: '#/components/parameters/ConversationToken'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Provide body or the ID of a native_chat/request_human reply. Popup, app-widget, and URL actions are dispatched by the Contact Button runtime.
              properties:
                body: {type: string, maxLength: 10000}
                suggested_reply_id: {type: string, format: uuid}
                button_id: {type: string, format: uuid}
                sender_type: {type: string, enum: [visitor, agent]}
                agent_context: {$ref: '#/components/schemas/AgentContext'}
      responses:
        '200': {$ref: '#/components/responses/Data'}
  /public/pages/{page}/conversations/{conversation}/request-human:
    post:
      tags: [Public actions]
      summary: Escalate a conversation to a human
      operationId: requestConversationHuman
      security: []
      parameters:
        - $ref: '#/components/parameters/PublicPage'
        - $ref: '#/components/parameters/ConversationId'
        - $ref: '#/components/parameters/ConversationToken'
      responses:
        '200': {$ref: '#/components/responses/Data'}
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Sanctum personal access token
      description: |
        A Contact Button scoped bearer key. Send `Authorization: Bearer <token>`.
        Token abilities are enforced independently of workspace membership; both checks must pass.
        OAuth 2.0 authorization-code flows are not currently available.
      x-contact-button-token-abilities:
        workspaces:read: Read workspaces, teams, domains, settings, and connections.
        workspaces:write: Create and manage workspaces, teams, domains, settings, and connections.
        content:read: Read buttons, forms, lists, pages, booking types, and knowledge.
        content:write: Create, update, publish, and remove content.
        engagements:read: Read submissions, subscribers, bookings, callbacks, calls, reviews, and conversations.
        engagements:manage: Reply to and manage engagement records.
        actions:request: Request protected actions that can still require human confirmation.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Stable 8–120 character key for safe mutation retries.
      schema: {type: string, minLength: 8, maxLength: 120}
    RequiredIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Stable 8–120 character key for safe payment creation retries.
      schema: {type: string, minLength: 8, maxLength: 120}
    WorkspaceId:
      name: workspace
      in: path
      required: true
      schema: {type: string, format: uuid}
    PageId:
      name: page
      in: path
      required: true
      schema: {type: string, format: uuid}
    PublicPage:
      name: page
      in: path
      required: true
      schema: {type: string, description: Published page UUID or slug.}
    DomainId:
      name: domain
      in: path
      required: true
      schema: {type: string, format: uuid}
    ButtonId:
      name: button
      in: path
      required: true
      schema: {type: string, format: uuid}
    FormId:
      name: form
      in: path
      required: true
      schema: {type: string, format: uuid}
    AppId:
      name: app
      in: path
      required: true
      schema: {type: string, format: uuid}
    ListId:
      name: list
      in: path
      required: true
      schema: {type: string, format: uuid}
    ArticleId:
      name: article
      in: path
      required: true
      schema: {type: string, format: uuid}
    SourceId:
      name: source
      in: path
      required: true
      schema: {type: string, format: uuid}
    ConversationId:
      name: conversation
      in: path
      required: true
      schema: {type: string, format: uuid}
    ChannelConnectionId:
      name: connection
      in: path
      required: true
      schema: {type: string, format: uuid}
    InboxThreadId:
      name: thread
      in: path
      required: true
      schema: {type: string, format: uuid}
    InboxMessageId:
      name: message
      in: path
      required: true
      schema: {type: string, format: uuid}
    InboxAttachmentId:
      name: attachment
      in: path
      required: true
      schema: {type: string, format: uuid}
    BookingId:
      name: booking
      in: path
      required: true
      schema: {type: string, format: uuid}
    CallbackId:
      name: callback
      in: path
      required: true
      schema: {type: string, format: uuid}
    CallFlowId:
      name: flow
      in: path
      required: true
      schema: {type: string, format: uuid}
    PhoneNumberId:
      name: number
      in: path
      required: true
      schema: {type: string, format: uuid}
    VoiceInteractionId:
      name: interaction
      in: path
      required: true
      schema: {type: string, format: uuid}
    ReviewId:
      name: review
      in: path
      required: true
      schema: {type: string, format: uuid}
    ConversationToken:
      name: X-Conversation-Token
      in: header
      required: true
      description: Opaque participant token returned only when the conversation is created.
      schema: {type: string, minLength: 32, maxLength: 128}
    Cursor:
      name: cursor
      in: query
      schema: {type: string}
  responses:
    Data:
      description: Successful data envelope.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/DataResponse'}
    PendingConfirmation:
      description: Request created; an OTP was sent to the represented human.
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/DataResponse'
    Problem:
      description: RFC 9457-style problem details.
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
  schemas:
    DataResponse:
      type: object
      required: [data, meta]
      properties:
        data: {}
        meta:
          type: object
          properties:
            request_id: {type: string}
            next_cursor: {type: [string, 'null']}
    Problem:
      type: object
      required: [type, title, status, detail, instance, request_id]
      properties:
        type: {type: string, format: uri}
        title: {type: string}
        status: {type: integer}
        detail: {type: string}
        instance: {type: string}
        request_id: {type: string}
        errors: {type: object, additionalProperties: {type: array, items: {type: string}}}
    SupportAppInput:
      type: object
      required: [active, configuration]
      properties:
        active: {type: boolean}
        configuration:
          type: object
          properties:
            title: {type: string, maxLength: 80}
            description: {type: [string, 'null'], maxLength: 500}
            support_term: {type: string, maxLength: 40}
            currency: {type: string, enum: [usd]}
            suggested_amounts:
              type: array
              minItems: 1
              maxItems: 6
              items: {type: integer, minimum: 200, maximum: 100000}
            allow_custom_amount: {type: boolean}
            minimum_amount: {type: integer, minimum: 200, maximum: 100000}
            maximum_amount: {type: integer, minimum: 200, maximum: 100000}
            allow_supporter_name: {type: boolean}
            allow_supporter_message: {type: boolean}
            allow_private: {type: boolean}
            show_supporter_count: {type: boolean}
            thank_you_title: {type: string, maxLength: 120}
            thank_you_message: {type: [string, 'null'], maxLength: 500}
    SupportPaymentInput:
      type: object
      required: [amount_minor]
      properties:
        amount_minor: {type: integer, minimum: 200, maximum: 100000}
        supporter_name: {type: [string, 'null'], maxLength: 120}
        supporter_message: {type: [string, 'null'], maxLength: 1000}
        private: {type: boolean, default: false}
    AgentSignupInput:
      type: object
      required: [human_email, display_name]
      properties:
        human_email: {type: string, format: email}
        display_name: {type: string, maxLength: 255}
        token_name: {type: string, maxLength: 100, default: agent}
    WorkspaceInput:
      type: object
      properties:
        name: {type: string, maxLength: 255}
        website_url: {type: string, format: uri}
    WorkspaceSettingsInput:
      type: object
      properties:
        notifications:
          type: object
          properties:
            lead_email: {type: boolean}
            booking_email: {type: boolean}
            callback_email: {type: boolean}
            chat_email: {type: boolean}
            recipients: {type: array, maxItems: 20, items: {type: string, format: email}}
        agent_policy:
          type: object
          properties:
            allow_agent_requests: {type: boolean}
            require_human_confirmation: {type: boolean}
            allow_agent_to_agent_chat: {type: boolean}
        inbox:
          type: object
          properties:
            mark_read_on_open: {type: boolean}
            reopen_closed_on_inbound: {type: boolean}
    InboxThreadStateInput:
      type: object
      minProperties: 1
      properties:
        unread: {type: boolean}
        starred: {type: boolean}
        status: {type: string, enum: [open, closed]}
    InboxMessageInput:
      type: object
      required: [channel]
      properties:
        contact_id: {type: string, format: uuid}
        conversation_id: {type: string, format: uuid}
        reply_to_message_id: {type: string, format: uuid}
        channel: {type: string, enum: [web_chat, email, whatsapp]}
        subject: {type: string, maxLength: 998}
        body: {type: string, maxLength: 50000}
        attachment_ids:
          type: array
          maxItems: 10
          items: {type: string, format: uuid}
        template_id: {type: string, format: uuid}
        template_parameters:
          type: array
          maxItems: 20
          items:
            type: object
            required: [type, parameters]
            properties:
              type: {type: string, enum: [header, body]}
              parameters:
                type: array
                items:
                  type: object
                  required: [type, text]
                  properties:
                    type: {type: string, const: text}
                    text: {type: string, maxLength: 1024}
    PageInput:
      type: object
      properties:
        name: {type: string, maxLength: 255}
        slug: {type: string, pattern: '^[a-z0-9_-]+$'}
        published: {type: boolean}
        publish_destination: {type: string, enum: [button_contact, custom]}
        display_name: {type: string}
        bio: {type: string, maxLength: 500}
        seo_title: {type: string}
        seo_description: {type: string}
        aeo_summary: {type: string}
        agent_instructions: {type: string, maxLength: 2000}
        chat: {$ref: '#/components/schemas/NativeChatInput'}
        schema_type: {type: string, enum: [WebPage, ProfilePage, ContactPage, AboutPage, Organization, LocalBusiness, Service]}
        robots_index: {type: boolean, default: true}
        llms_enabled: {type: boolean, default: true}
    NativeChatInput:
      type: object
      properties:
        greeting: {type: string, maxLength: 500}
        suggested_replies:
          type: array
          maxItems: 6
          items: {$ref: '#/components/schemas/SuggestedReplyInput'}
    SuggestedReplyInput:
      type: object
      description: A typed Contact Button action. Legacy operation-only replies remain supported as native chat actions.
      required: [id, label, event_label]
      properties:
        id: {type: string, format: uuid}
        kind:
          type: string
          enum: [answer_link, call, callback, contact_form, email, extra_action, faq, get_quote, question, browse, link_url, app_widget, livechat, newsletter, schedule_meeting, schedule_video_call, video, video_call, whatsapp, whatsapp_form]
        label: {type: string, maxLength: 120}
        message: {type: string, maxLength: 10000}
        featured: {type: boolean, default: false}
        event_label: {type: string, maxLength: 80}
        operation: {type: string, enum: [message, request_human], default: message}
        action: {$ref: '#/components/schemas/SuggestedReplyAction'}
    SuggestedReplyAction:
      type: object
      required: [type]
      properties:
        type: {type: string, enum: [native_chat, request_human, url, app_widget, popup]}
        target:
          type: [string, 'null']
          description: An http(s), tel, or mailto URL; an installed app public ID; or a popup external ID, depending on type.
        new_tab:
          type: boolean
          default: false
          description: Applies only to http(s) URL actions.
    ButtonInput:
      type: object
      properties:
        title: {type: string}
        texts: {type: object}
        display_option: {type: string, enum: [columns, rows, stacked_buttons, icons_only, single_app]}
        template:
          readOnly: true
          oneOf:
            - {$ref: '#/components/schemas/ButtonTemplateReference'}
            - {type: 'null'}
        position: {type: string, enum: [bottom_right, bottom_left, top_right, top_left]}
        default_state: {type: string, enum: [text_button, closed_circle, button_with_text]}
        colors:
          type: object
          description: Launcher button and menu bar appearance. Existing buttons default to solid, fully opaque colors.
          properties:
            outside_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$', description: Solid launcher color and legacy fallback.}
            button_background_type: {type: string, enum: [solid_color, gradient], default: solid_color}
            button_opacity: {type: integer, minimum: 0, maximum: 100, default: 100}
            button_gradient_top_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            button_gradient_top_opacity: {type: integer, minimum: 0, maximum: 100, default: 100}
            button_gradient_bottom_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            button_gradient_bottom_opacity: {type: integer, minimum: 0, maximum: 100, default: 100}
            menu_bar_background_type: {type: string, enum: [solid_color, gradient], default: solid_color}
            menu_bar_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            menu_bar_gradient_top_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            menu_bar_gradient_top_opacity: {type: integer, minimum: 0, maximum: 100, default: 100}
            menu_bar_gradient_bottom_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            menu_bar_gradient_bottom_opacity: {type: integer, minimum: 0, maximum: 100, default: 100}
        panel: {$ref: '#/components/schemas/ButtonPanel'}
        backgrounds: {$ref: '#/components/schemas/ButtonBackgrounds'}
        desktop: {type: boolean}
        tablet: {type: boolean, description: Visible from 768px through 1023px. Defaults to mobile for legacy payloads.}
        mobile: {type: boolean}
        visibility: {$ref: '#/components/schemas/DeviceVisibility'}
        status: {type: boolean}
        launcher: {$ref: '#/components/schemas/LauncherSettings'}
        app_ids:
          type: array
          description: Unique app IDs in flat order. Supplying this without navigation creates a flat root menu.
          items: {type: string, format: uuid}
        navigation: {$ref: '#/components/schemas/ButtonNavigation'}
        page_ids: {type: array, items: {type: string, format: uuid}}
    ButtonPanel:
      type: object
      description: Optional panel styling and owner-configured Messenger shell. A null layout preserves saved tabs while the legacy panel remains active.
      properties:
        version: {type: integer, enum: [1], readOnly: true}
        layout: {type: [string, 'null'], enum: [messenger, null]}
        tabs:
          type: array
          items: {$ref: '#/components/schemas/ButtonPanelTab'}
        header:
          type: object
          properties:
            separator:
              type: object
              additionalProperties: false
              properties:
                style: {type: string, enum: [straight, wave, curve, arc, angle]}
                depth: {type: string, enum: [small, medium, large]}
            padding:
              type: object
              additionalProperties: false
              properties:
                preset: {type: string, enum: [compact, comfortable, spacious]}
    ButtonPanelTab:
      type: object
      required: [id, label, icon, destination]
      properties:
        id: {type: string, format: uuid}
        label: {type: string, maxLength: 40}
        icon: {type: string, enum: [home, apps, message, chat, help, search, phone, whatsapp, calendar, link, info]}
        destination:
          oneOf:
            - type: object
              required: [type, app_id]
              properties:
                type: {type: string, enum: [app]}
                app_id: {type: string, format: uuid}
            - type: object
              required: [type, view]
              properties:
                type: {type: string, enum: [view]}
                view: {type: string, enum: [navigation_root]}
            - type: object
              required: [type, page_id]
              description: Native chat uses the linked published page's greeting and suggested replies.
              properties:
                type: {type: string, enum: [chat]}
                page_id: {type: string, format: uuid}
    LauncherSettings:
      type: object
      description: Versioned launcher appearance. Legacy buttons are projected into this shape without opting into the new renderer.
      properties:
        version: {type: integer, enum: [1], default: 1}
        shape: {type: string, enum: [circle, rounded_square, square, pill]}
        size: {type: string, enum: [compact, standard, large, custom]}
        width: {type: integer, minimum: 44, maximum: 200}
        height: {type: integer, minimum: 44, maximum: 96}
        background:
          type: object
          properties:
            type: {type: string, enum: [legacy, solid, linear_gradient]}
            color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            colors:
              type: array
              minItems: 2
              maxItems: 2
              items: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            angle: {type: integer, minimum: 0, maximum: 360}
        border:
          type: object
          properties:
            width: {type: integer, minimum: 0, maximum: 8}
            color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            ring_width: {type: integer, minimum: 0, maximum: 8}
            ring_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
        icon:
          type: object
          properties:
            size: {type: integer, minimum: 16, maximum: 48}
            color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
        device_overrides:
          type: object
          additionalProperties: {type: object}
        attention:
          $ref: '#/components/schemas/LauncherAttention'
        text:
          $ref: '#/components/schemas/LauncherText'
    LauncherAttention:
      type: object
      properties:
        animation:
          type: string
          enum: [none, bounce, waggle, float, sheen, spin, fade, shockwave, pulse, blink]
          default: none
        delay_ms: {type: integer, minimum: 0, maximum: 30000, default: 0}
        play: {type: string, enum: [once_per_page], default: once_per_page}
    LauncherText:
      type: object
      properties:
        hide_after_ms:
          type: [integer, 'null']
          minimum: 1000
          maximum: 300000
          description: Collapse launcher text after this many milliseconds from its first visible render. Null disables the timer.
    ButtonLauncher:
      allOf:
        - {$ref: '#/components/schemas/LauncherSettings'}
    ButtonBackgrounds:
      type: object
      additionalProperties: false
      properties:
        launcher: {$ref: '#/components/schemas/ButtonBackground'}
        panel: {$ref: '#/components/schemas/ButtonBackground'}
        chat: {$ref: '#/components/schemas/ButtonBackground'}
    ButtonBackground:
      type: object
      required: [type]
      additionalProperties: false
      properties:
        type: {type: string, enum: [solid, linear_gradient, image, transparent]}
        color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
        colors:
          type: array
          minItems: 2
          maxItems: 2
          items: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
        angle: {type: integer, minimum: 0, maximum: 360}
        asset_id: {type: string, format: uuid}
        fit: {type: string, enum: [cover, contain]}
        position: {type: string, enum: [center, top, bottom, left, right]}
        fallback_color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
        overlay:
          type: object
          additionalProperties: false
          properties:
            color: {type: string, pattern: '^#[0-9a-fA-F]{6}$'}
            opacity: {type: integer, minimum: 0, maximum: 100}
    ButtonNavigation:
      type: object
      description: Authoritative button-local tree. When app_ids is also supplied, the unique app sets must match. The configured safety cap applies to the total number of nodes across all levels.
      required: [items]
      additionalProperties: false
      properties:
        items:
          type: array
          maxItems: 250
          items: {$ref: '#/components/schemas/ButtonNavigationNode'}
    ButtonNavigationNode:
      oneOf:
        - type: object
          required: [id, type, app_id]
          additionalProperties: false
          properties:
            id: {type: string, format: uuid}
            type: {type: string, enum: [app]}
            app_id: {type: string, format: uuid}
        - type: object
          required: [id, type, label, children]
          additionalProperties: false
          properties:
            id: {type: string, format: uuid}
            type: {type: string, enum: [folder]}
            label: {type: string, minLength: 1, maxLength: 80}
            children:
              type: array
              items: {$ref: '#/components/schemas/ButtonNavigationNode'}
    ButtonTemplateReference:
      type: object
      required: [key, version]
      properties:
        key: {type: string, enum: [ecommerce, local-business, restaurant, omnichannel, social-links, click-to-call, contact-form, whatsapp-chat]}
        version: {type: integer, enum: [1]}
    ButtonTemplateInstantiation:
      type: object
      required: [title, submission_id]
      properties:
        title: {type: string, maxLength: 255, description: Internal button name.}
        button_text: {type: string, maxLength: 80, nullable: true}
        submission_id: {type: string, format: uuid, description: Stable identifier used to make a single template submission idempotent.}
    FormInput:
      type: object
      properties:
        name: {type: string}
        fields:
          type: array
          items: {$ref: '#/components/schemas/FormField'}
        settings: {type: object}
        design: {type: object}
        integrations: {type: object}
        embed: {type: object}
    FormField:
      type: object
      required: [id, type]
      properties:
        id: {type: string}
        type: {type: string, enum: [text, email, tel, textarea, select, checkbox, radio, hidden]}
        label: {type: string}
        required: {type: boolean}
        options: {type: array, items: {type: string}}
    SurveyInput:
      type: object
      properties:
        name: {type: string, maxLength: 255}
        slug: {type: string, pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$'}
        description: {type: string, maxLength: 2000}
        status: {type: string, enum: [draft, published, paused, archived]}
        questions:
          type: array
          minItems: 1
          maxItems: 100
          items: {$ref: '#/components/schemas/SurveyQuestion'}
        settings: {type: object}
        design: {type: object}
        distribution: {type: object}
    SurveyQuestion:
      type: object
      required: [id, type, title]
      properties:
        id: {type: string, pattern: '^[A-Za-z0-9_-]{3,80}$'}
        type: {type: string, enum: [nps, rating, choice, multiple_choice, text]}
        title: {type: string, maxLength: 500}
        description: {type: string, maxLength: 1000}
        required: {type: boolean}
        options: {type: array, maxItems: 50, items: {type: string, maxLength: 255}}
        settings:
          type: object
          properties:
            scale: {type: integer, minimum: 2, maximum: 10}
            low_label: {type: string}
            high_label: {type: string}
            branching:
              type: object
              required: [answer, next_question_id]
              properties:
                answer:
                  oneOf:
                    - {type: string}
                    - {type: number}
                next_question_id: {type: string}
    SurveyResponseInput:
      type: object
      required: [answers]
      properties:
        channel: {type: string, enum: [web, api, sms, whatsapp, email, embed]}
        respondent:
          type: object
          properties:
            name: {type: string}
            email: {type: string, format: email}
            phone: {type: string}
            external_id: {type: string}
        answers:
          type: object
          description: Map of stable survey question IDs to validated answer values.
          additionalProperties: true
        started_at: {type: string, format: date-time}
    SurveyDistributionInput:
      type: object
      required: [channel, recipient, consent]
      properties:
        channel: {type: string, enum: [sms, whatsapp]}
        recipient: {type: string, pattern: '^\+[1-9][0-9]{7,14}$'}
        locale: {type: string, default: en}
        template_reference: {type: string, description: Required for WhatsApp Business delivery.}
        message: {type: string, maxLength: 1000}
        scheduled_at: {type: string, format: date-time}
        consent:
          type: object
          required: [granted]
          properties:
            granted: {type: boolean}
            captured_at: {type: string, format: date-time}
            source: {type: string}
    AppInput:
      type: object
      properties:
        catalog_slug: {type: string}
        title: {type: string}
        fields: {type: object, additionalProperties: true}
        desktop: {type: boolean}
        tablet: {type: boolean, description: Visible from 768px through 1023px. Defaults to mobile for legacy payloads.}
        mobile: {type: boolean}
        visibility: {$ref: '#/components/schemas/DeviceVisibility'}
        status: {type: boolean}
        resource_type: {type: [string, 'null'], enum: [form, list, null]}
        resource_id: {type: [string, 'null'], format: uuid}
    DeviceVisibility:
      type: object
      required: [desktop, tablet, mobile]
      additionalProperties: false
      description: Viewport visibility using mobile below 768px, tablet from 768px through 1023px, and desktop at 1024px or wider.
      properties:
        desktop: {type: boolean}
        tablet: {type: boolean}
        mobile: {type: boolean}
    BookingEventTypeInput:
      type: object
      required: [name, duration_minutes, timezone, availability]
      properties:
        project_id: {type: [string, 'null'], format: uuid}
        name: {type: string}
        slug: {type: string}
        description: {type: string}
        duration_minutes: {type: integer, minimum: 5, maximum: 480}
        buffer_before_minutes: {type: integer, minimum: 0}
        buffer_after_minutes: {type: integer, minimum: 0}
        minimum_notice_minutes: {type: integer, minimum: 0}
        booking_window_days: {type: integer, minimum: 1, maximum: 365}
        timezone: {type: string, example: Europe/Amsterdam}
        location_type: {type: string, enum: [phone, video, in_person, custom]}
        location: {type: string}
        confirmation_mode: {type: string, enum: [email_otp, automatic], default: email_otp}
        availability:
          type: array
          items:
            type: object
            required: [day_of_week, starts_at, ends_at]
            properties:
              day_of_week: {type: integer, minimum: 1, maximum: 7}
              starts_at: {type: string, pattern: '^([01][0-9]|2[0-3]):[0-5][0-9]$'}
              ends_at: {type: string, pattern: '^([01][0-9]|2[0-3]):[0-5][0-9]$'}
    TelephonyConnectionInput:
      type: object
      description: Initial setup requires both credentials. Omit saved credentials on later capability updates.
      properties:
        api_key:
          type: string
          minLength: 20
          maxLength: 500
          writeOnly: true
          description: Telnyx API V2 key. Keep this out of model context and logs.
        webhook_public_key:
          type: string
          minLength: 32
          maxLength: 2000
          writeOnly: true
          description: Telnyx account Ed25519 public key used to verify webhook signatures.
        inbound_calls: {type: boolean}
        call_forwarding: {type: boolean}
        outbound_calling: {type: boolean}
        sms_lead_alerts: {type: boolean}
        default_sms_sender:
          type: string
          pattern: '^\+[1-9][0-9]{7,14}$'
          description: Required when SMS lead alerts are enabled.
    CallFlowInput:
      type: object
      required: [name, mode, language, tts_voice, max_recording_seconds, missed_call_sms_delay_seconds]
      properties:
        name: {type: string, maxLength: 120}
        mode: {type: string, enum: [ai_agent, forwarding, voicemail, callback]}
        provider_assistant_id:
          type: [string, 'null']
          maxLength: 255
          description: Required for ai_agent mode. Use an assistant ID from the connected Telnyx account.
        forwarding_number:
          type: [string, 'null']
          pattern: '^\+[1-9][0-9]{7,14}$'
        caller_id_voice_number_id:
          type: [integer, 'null']
          deprecated: true
          description: Optional internal compatibility identifier. Omit unless returned by an authorized Contact Button client.
        greeting:
          type: [string, 'null']
          maxLength: 2000
          description: Required for voicemail and callback modes.
        language: {type: string, maxLength: 16, example: en-US}
        tts_voice: {type: string, maxLength: 120, example: Telnyx.NaturalHD.astra}
        notification_emails:
          type: array
          maxItems: 10
          items: {type: string, format: email}
        notification_phones:
          type: array
          maxItems: 10
          items: {type: string, pattern: '^\+[1-9][0-9]{7,14}$'}
        max_recording_seconds: {type: integer, minimum: 10, maximum: 300}
        fallback_to_voicemail: {type: boolean, default: false}
        missed_call_sms_enabled: {type: boolean, default: false}
        missed_call_sms_template: {type: [string, 'null'], maxLength: 1000}
        missed_call_sms_delay_seconds: {type: integer, minimum: 0, maximum: 3600, default: 30}
        is_active: {type: boolean, default: true}
    PhoneNumberSearchInput:
      type: object
      required: [country_code]
      properties:
        country_code: {type: string, pattern: '^[A-Za-z]{2}$', example: NL}
        area_code: {type: [string, 'null'], pattern: '^[0-9]+$', maxLength: 10, example: '20'}
        sms: {type: boolean, default: false}
    PhoneNumberOrderInput:
      type: object
      required: [quote, label, call_flow_id, confirm_price]
      properties:
        quote:
          type: string
          format: uuid
          description: Unexpired opaque quote ID returned by phone-number search.
        label: {type: string, maxLength: 120}
        call_flow_id:
          type: string
          format: uuid
          description: Public UUID returned by a call-flow operation.
        confirm_price:
          type: boolean
          const: true
          description: Send only after the human explicitly approves the quoted recurring and upfront charges.
    KnowledgeListInput:
      type: object
      required: [title]
      properties:
        title: {type: string}
        slug: {type: string}
        description: {type: string}
        default_visibility: {type: string, enum: [workspace, public, roles]}
    KnowledgeArticleInput:
      type: object
      properties:
        title: {type: string}
        slug: {type: string}
        excerpt: {type: string}
        body_markdown: {type: string}
        locale: {type: string, default: en}
        tags: {type: array, items: {type: string}}
        visibility: {type: string, enum: [workspace, public, roles]}
        publish: {type: boolean, default: false}
        action: {type: object}
    KnowledgeSourceInput:
      type: object
      required: [provider, name]
      properties:
        provider: {type: string, enum: [rss, markdown, github, google_drive, onedrive]}
        name: {type: string}
        source_url: {type: [string, 'null'], format: uri}
        configuration: {type: object}
        access_token: {type: string, writeOnly: true}
        mode: {type: string, enum: [import, continuous]}
        trusted_auto_publish: {type: boolean, default: false}
        sync_frequency: {type: string, enum: [manual, hourly, daily]}
    AgentContext:
      type: object
      required: [name]
      properties:
        name: {type: string}
        provider: {type: string}
        identifier: {type: string}
        acting_for: {type: string}
    SubscriptionRequest:
      type: object
      required: [email]
      properties:
        email: {type: string, format: email}
        name: {type: string}
        requested_by: {type: string, enum: [human, agent]}
        agent_context: {$ref: '#/components/schemas/AgentContext'}
    AgentFormSubmissionRequest:
      type: object
      required: [human_email, answers, requested_by, agent_context]
      properties:
        human_email:
          type: string
          format: email
          description: Address that receives the one-time confirmation code.
        answers:
          type: object
          maxProperties: 50
          description: Values matching the live input_schema advertised for this form in the page manifest.
          additionalProperties: true
        requested_by: {type: string, const: agent}
        agent_context: {$ref: '#/components/schemas/AgentContext'}
    BookingRequest:
      type: object
      required: [starts_at, timezone, email]
      properties:
        starts_at: {type: string, format: date-time}
        timezone: {type: string}
        email: {type: string, format: email}
        name: {type: string}
        phone: {type: string}
        notes: {type: string, maxLength: 4000}
        answers: {type: object}
        requested_by: {type: string, enum: [human, agent]}
        agent_context: {$ref: '#/components/schemas/AgentContext'}
    CallbackRequest:
      type: object
      required: [phone, email, timezone]
      properties:
        phone: {type: string}
        email: {type: string, format: email}
        name: {type: string}
        timezone: {type: string}
        window_starts_at: {type: string, format: date-time}
        window_ends_at: {type: string, format: date-time}
        subject: {type: string}
        notes: {type: string}
        requested_by: {type: string, enum: [human, agent]}
        agent_context: {$ref: '#/components/schemas/AgentContext'}
    ReviewRequestInput:
      type: object
      required: [contact_id, destination_id, channel, consent_confirmed]
      properties:
        contact_id: {type: string, format: uuid}
        destination_id: {type: string, format: uuid}
        channel: {type: string, enum: [email, sms, whatsapp]}
        message:
          type: [string, 'null']
          maxLength: 1400
          description: Invitation copy supporting {contact}, {business}, and {review_link} tokens.
        template_id:
          type: [string, 'null']
          format: uuid
          description: Required for WhatsApp and must identify an approved template on this workspace's connected Meta account.
        template_parameters:
          type: array
          maxItems: 10
          items: {type: [string, 'null'], maxLength: 1024}
        scheduled_at: {type: [string, 'null'], format: date-time}
        consent_confirmed: {type: boolean, const: true}
        consent_source: {type: [string, 'null'], maxLength: 255}
    ConversationInput:
      type: object
      description: Provide message or suggested_reply_id. Suggested reply messages and event labels are always resolved server-side.
      properties:
        mode: {type: string, enum: [human, agent_assisted, agent_to_agent]}
        visitor_name: {type: string}
        visitor_email: {type: string, format: email}
        subject: {type: string}
        message: {type: string, maxLength: 10000}
        suggested_reply_id: {type: string, format: uuid}
        button_id: {type: string, format: uuid}
        requested_by: {type: string, enum: [human, agent]}
        agent_context: {$ref: '#/components/schemas/AgentContext'}
