openapi: 3.0.3
info:
  title: Unpod API
  description: REST API for Unpod Voice AI Platform
  version: 2.0.0

servers:
  - url: https://unpod.ai/
    description: QA
  - url: https://unpod.ai/
    description: Production

tags:
  - name: Organisation
  - name: Spaces
  - name: Agents
  - name: Tasks
  - name: Runs
  - name: Call Logs
  - name: Analytics
  - name: Providers
  - name: Bridges
  - name: Numbers
  - name: Trunks
  - name: Telephony
  - name: Billing

paths:

  # ─── Organisation ────────────────────────────────────────────
  /api/v2/platform/organizations/:
    get:
      operationId: getOrganizations
      summary: Get Organizations
      tags: [Organisation]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of organizations
          content:
            application/json:
              example:
                count: 3
                status_code: 200
                message: "Organizations fetched successfully"
                data:
                  - id: 1
                    name: "Unpod TV"
                    domain_handle: "unpod.tv"
                    created_at: "2024-01-15T10:30:00Z"
                  - id: 2
                    name: "Recalll"
                    domain_handle: "recalll.co"
                    created_at: "2024-03-20T08:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Spaces ──────────────────────────────────────────────────
  /api/v2/platform/spaces/:
    get:
      operationId: getAllSpaces
      summary: Get All Spaces
      tags: [Spaces]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of all spaces
          content:
            application/json:
              example:
                count: 207
                status_code: 200
                message: "Spaces fetched successfully"
                data:
                  - id: "sp_001"
                    name: "Sales Outreach Q1"
                    token: "8KZAMRAHSXXXXXXMAYNASMJC"
                    agent_handle: "space-agent-8qmk42nslp91wrh3dz7btxc4"
                    created_at: "2026-01-10T08:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/spaces/{space_token}/:
    get:
      operationId: getSpaceByToken
      summary: Get Space by Token
      tags: [Spaces]
      security:
        - TokenAuth: []
      parameters:
        - name: space_token
          in: path
          required: true
          schema:
            type: string
            example: "8KZAMRAHSXXXXXXMAYNASMJC"
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: Space details
          content:
            application/json:
              example:
                status_code: 200
                message: "Space fetched successfully"
                data:
                  id: "sp_001"
                  name: "Sales Outreach Q1"
                  token: "8KZRTQP7BNW5XEDLORYUHMJC"
                  agent_handle: "space-agent-8qmk42nslp91wrh3dz7btxc4"
                  created_at: "2026-01-10T08:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v2/platform/spaces/{space_token}/tasks/:
    get:
      operationId: getTasksBySpaceToken
      summary: Get Tasks by Space Token
      tags: [Spaces]
      security:
        - TokenAuth: []
      parameters:
        - name: space_token
          in: path
          required: true
          schema:
            type: string
            example: "8KZAMRAHSXXXXXXMAYNASMJC"
        - name: page
          in: query
          schema:
            type: integer
            example: 1
        - name: page_size
          in: query
          schema:
            type: integer
            example: 20
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of tasks in the space
          content:
            application/json:
              example:
                count: 45
                status_code: 200
                message: "Tasks Fetched Successfully"
                data:
                  - _id: "697debb84c27faa892bfa0cc"
                    thread_id: "thread_9xkL2mQpR7vNwY4sZ3cJ8hF1"
                    user_info:
                      email: "user@example.com"
                      full_name: "John Doe"
                    task_id: "T8ff2ccdffe9a11f0878d43cd8a99e069"
                    run_id: "R8ff2ccdefe9a11f0878d43cd8a99e069"
                    task:
                      objective: "Call the lead and discuss the project requirements."
                    input:
                      name: "John Doe"
                      contact_number: "1234567890"
                      email: "john@example.com"
                      context: "Follow up on proposal"
                    output:
                      call_id: "CALL_7dAb3kR9mXvQ2pLw"
                      start_time: "2026-02-07T05:57:45Z"
                      end_time: "2026-02-07T06:02:30Z"
                      duration: 285
                      recording_url: "https://cdn.unpod.dev/recordings/CALL_7dAb3kR9mXvQ2pLw.mp3"
                      transcript:
                        - role: "agent"
                          content: "Hello, this is an AI assistant calling on behalf of Unpod. Am I speaking with John Doe?"
                        - role: "user"
                          content: "Yes, this is John."
                      post_call_data:
                        summary: "The agent successfully connected with John Doe and discussed project requirements."
                        outcome: "interested"
                        sentiment: "positive"
                      call_type: "outbound"
                      call_status: "completed"
                    assignee: "space-agent-8qmk42nslp91wrh3dz7btxc4"
                    status: "completed"
                    execution_type: "call"
                    run_mode: "prod"
                    created: "2026-02-07T05:57:45Z"
                    modified: "2026-02-07T06:02:35Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Agents ──────────────────────────────────────────────────
  /api/v2/platform/agents/:
    get:
      operationId: getAllAgents
      summary: Get All Agents
      tags: [Agents]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of all agents
          content:
            application/json:
              example:
                count: 94
                status_code: 200
                message: "Agents fetched successfully"
                data:
                  - handle: "space-agent-8qmk42nslp91wrh3dz7btxc4"
                    name: "General Agentic"
                    type: "Voice"
                    state: "published"
                    purpose: "Handle outbound sales calls"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/agents/{agent_handle}/tasks/:
    get:
      operationId: getTasksByAgentHandle
      summary: Get Tasks by Agent Handle
      tags: [Agents]
      security:
        - TokenAuth: []
      parameters:
        - name: agent_handle
          in: path
          required: true
          schema:
            type: string
            example: "space-agent-8qmk42nslp91wrh3dz7btxc4"
        - name: page
          in: query
          schema:
            type: integer
            example: 1
        - name: page_size
          in: query
          schema:
            type: integer
            example: 20
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of tasks for the agent
          content:
            application/json:
              examples:
                v1:
                  summary: Legacy response (v1)
                  value:
                    count: 1
                    status_code: 200
                    message: Tasks Fetched Successfully
                    data:
                    - _id: 697debb84c27faa892bfa0cc
                      thread_id: thread_9xkL2mQpR7vNwY4sZ3cJ8hF1
                      user_info:
                        email: user@example.com
                        full_name: John Doe
                      task_id: T8ff2ccdffe9a11f0878d43cd8a99e069
                      run_id: R8ff2ccdefe9a11f0878d43cd8a99e069
                      task:
                        objective: Call the lead and discuss the project requirements.
                      input:
                        name: John Doe
                        contact_number: '1234567890'
                        email: john@example.com
                        context: Follow up on proposal
                      output:
                        call_id: CALL_7dAb3kR9mXvQ2pLw
                        call_end_reason: caller_hangup
                        start_time: '2026-02-07T05:57:45Z'
                        end_time: '2026-02-07T06:02:30Z'
                        assistant_number: '+911234567890'
                        call_summary: The agent successfully connected with John Doe and discussed project requirements.
                        duration: 285
                        recording_url: https://cdn.unpod.dev/recordings/CALL_7dAb3kR9mXvQ2pLw.mp3
                        transcript:
                        - role: agent
                          content: Hello, this is an AI assistant. Am I speaking with John Doe?
                        - role: user
                          content: Yes, this is John.
                        post_call_data:
                          summary: Agent connected with John Doe and discussed project requirements.
                          outcome: interested
                          sentiment: positive
                        call_type: outbound
                        call_status: completed
                      attachments: []
                      assignee: space-agent-8qmk42nslp91wrh3dz7btxc4
                      status: completed
                      execution_type: call
                      ref_id: 6902fb5840a736e125e80ebc
                      failure_count: 0
                      last_failure_reason: null
                      retry_attempt: 0
                      last_status_change: '2026-02-07T06:02:35Z'
                      scheduled_timestamp: null
                v2:
                  summary: Current response (v2) - grouped post_call_data, schema_version 2
                  value:
                    count: 38
                    status_code: 200
                    message: Tasks Fetched Successfully
                    data:
                    - _id: 697debb84c27faa892bfa0cd
                      space_id: '42'
                      thread_id: ''
                      user: ''
                      user_org_id: '101'
                      user_info: {}
                      created: '2026-02-07T05:51:51.607000'
                      modified: '2026-02-07T05:54:03.857000'
                      task_id: T1a2b3c4dfe9a11f0878d43cd8a99e069
                      run_id: R1a2b3c4cfe9a11f0878d43cd8a99e069
                      collection_ref: collection_data_SPTKN4XQ7M2VB9YH3KD8WZ1L
                      task: {}
                      input:
                        contact_number: '+911234567890'
                        quality: good
                        name: ''
                        token: SPTKN4XQ7M2VB9YH3KD8WZ1L
                        space_token: SPTKN4XQ7M2VB9YH3KD8WZ1L
                        org_id: 101
                        missing_space_context: false
                      output:
                        call_type: inbound
                        call_status: connected
                        call_end_reason: USER_HUNG_UP_IN_CALL
                      attachments: []
                      assignee: space-agent-8qmk42nslp91wrh3dz7btxc4
                      status: in_progress
                      execution_type: call
                      ref_id: 6902fb5840a736e125e80ebc
                      failure_count: 0
                      last_failure_reason: null
                      provider: null
                      retry_attempt: 0
                      referenced_task: null
                      follow_up_count: null
                      follow_up_chain_id: null
                      last_status_change: '2026-02-07T05:51:51.606885'
                      scheduled_timestamp: null
                    - _id: 697debb84c27faa892bfa0ce
                      space_id: '42'
                      thread_id: ''
                      user: ''
                      user_org_id: '101'
                      user_info: {}
                      created: '2026-02-07T05:47:38.682000'
                      modified: '2026-02-07T05:51:28.673000'
                      task_id: T5e6f7a8bfe9a11f0878d43cd8a99e069
                      run_id: R5e6f7a8afe9a11f0878d43cd8a99e069
                      collection_ref: collection_data_SPTKN4XQ7M2VB9YH3KD8WZ1L
                      task: {}
                      input:
                        contact_number: '+911234567890'
                        quality: good
                        name: ''
                        token: SPTKN4XQ7M2VB9YH3KD8WZ1L
                        space_token: SPTKN4XQ7M2VB9YH3KD8WZ1L
                        org_id: 101
                        missing_space_context: false
                      output:
                        call_id: Unpod Bridge_1234567890_jC8KHDuwGuBi
                        customer: ''
                        contact_number: '+911234567890'
                        call_end_reason: BLOCKED_USER
                        recording_url: null
                        transcript: []
                        start_time: '2026-02-07T05:47:39.119969'
                        end_time: '2026-02-07T05:47:39.119980'
                        assistant_number: '+919876543210'
                        call_summary: null
                        duration: 0
                        cost: 0.0
                        post_call_data:
                          schema_version: 2
                          analytics:
                            sentiment: null
                            engagement: null
                            tone: null
                            contact_status: active
                            outcome: NotConnected
                            call_score: null
                            call_score_breakdown: null
                          summary:
                            headline: null
                            key_points: []
                          lead:
                            name: null
                            labels: []
                            objections: []
                            pain_points: []
                            questions_asked: []
                          structured_data:
                            outcome: null
                            payment_release_choice: null
                          next_action:
                            label: []
                            callback_requested: false
                            scheduled_at: null
                          follow_up:
                            required: true
                            state: skipped_inbound_call
                            reason: not-connected retry
                            next_at: null
                            attempt: 1
                            attempts_remaining: 2
                            policy:
                              max_call_attempts: 3
                              max_followup_attempts: 2
                              daily_limit: 3
                              next_call_attempt_number: 2
                          evaluation: null
                          call_evaluation: null
                          redial:
                            status: null
                          skipped:
                          - stage: call_evaluation
                            reason: policy:disabled_in_config
                          - stage: critic
                            reason: no_invariant_violations
                          post_call_usage:
                            thread_id: T5e6f7a8bfe9a11f0878d43cd8a99e069
                            room_name: ''
                            agent_id: space-agent-8qmk42nslp91wrh3dz7btxc4
                            org_id: null
                            space_token: null
                            call_start: '2026-02-07T05:51:28.326798Z'
                            call_end: '2026-02-07T05:51:28.326798Z'
                            call_duration_s: '0.00'
                            providers:
                              llm_provider: Unpod
                              llm_model: google/gemma-4-31b-it
                              stt_provider: soniox
                              stt_model: stt-rt-v5
                              tts_provider: cartesia
                              tts_model: sonic-3.5
                            raw_counters:
                              llm_prompt_tokens: '1508'
                              llm_completion_tokens: '127'
                              llm_cached_tokens: '0'
                              tts_characters_count: '0'
                              stt_audio_duration: '0'
                            sku_deltas:
                            - codename: llm.unpod.google/gemma_4_31b_it.input
                              raw: '1508'
                              unit_scale: '1'
                            - codename: llm.unpod.google/gemma_4_31b_it.output
                              raw: '127'
                              unit_scale: '1'
                        metadata:
                          cost: 0.0
                          type: ''
                          usage: {}
                        call_type: inbound
                        call_status: failed
                      attachments: []
                      assignee: space-agent-8qmk42nslp91wrh3dz7btxc4
                      status: completed
                      execution_type: call
                      ref_id: 6902fb5840a736e125e80ebc
                      failure_count: 0
                      last_failure_reason: null
                      provider: null
                      retry_attempt: 0
                      referenced_task: null
                      follow_up_count: null
                      follow_up_chain_id: null
                      last_status_change: '2026-02-07T05:47:38.682017'
                      scheduled_timestamp: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v2/platform/agents/{agent_handle}/executions/:
    get:
      operationId: getAgentExecutions
      summary: Get All Agent Executions
      tags: [Agents]
      security:
        - TokenAuth: []
      parameters:
        - name: agent_handle
          in: path
          required: true
          schema:
            type: string
            example: "space-agent-8qmk42nslp91wrh3dz7btxc4"
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of executions for the agent
          content:
            application/json:
              example:
                count: 12
                status_code: 200
                message: "Executions fetched successfully"
                data:
                  - run_id: "Recac64fe03e911f1878d43cd8a99e069"
                    status: "completed"
                    total_tasks: 25
                    completed_tasks: 23
                    created: "2026-02-07T05:57:45Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─── Tasks / Make Call ────────────────────────────────────────
  /api/v2/platform/spaces/{space_token}/tasks/create/:
    post:
      operationId: createTask
      summary: Create a Task - Make Voice AI Call
      tags: [Tasks]
      security:
        - TokenAuth: []
      parameters:
        - name: space_token
          in: path
          required: true
          schema:
            type: string
            example: "8KZAMRAHSXXXXXXMAYNASMJC"
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pilot
                - documents
              properties:
                pilot:
                  type: string
                  description: Agent/pilot handle to assign the task
                  example: "space-agent-f1o3qjm1y7q1avvuynv4vprb1"
                documents:
                  type: array
                  description: Array of contact/document objects
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                        example: "John Doe"
                      email:
                        type: string
                        example: "john@example.com"
                      contact_number:
                        type: string
                        example: "1234567890"
                      alternate_number:
                        type: string
                        example: "+919876543210"
                      occupation:
                        type: string
                        example: "Sales Manager"
                      company_name:
                        type: string
                        example: "Acme Corp"
                      address:
                        type: string
                        example: "123 Main St, Mumbai"
                      about:
                        type: string
                        example: "Warm lead from webinar"
                      context:
                        type: string
                        example: "Follow up on proposal sent last week"
                      labels:
                        type: array
                        items:
                          type: string
                        example: ["warm-lead", "webinar"]
                      title:
                        type: string
                        example: "Q1 Outreach"
                      description:
                        type: string
                        example: "Lead from Q1 campaign"
                      document_id:
                        type: string
                        example: "6902fb5840a736e125e80ebc"
                context:
                  type: string
                  description: Additional context for the task
                  example: "Call the lead and discuss the project requirements."
                schedule:
                  type: object
                  description: Schedule configuration
                  example:
                    type: "now"
            example:
              pilot: "space-agent-f1o3qjm1y7q1avvuynv4vprb1"
              context: "Call the lead and discuss the project requirements."
              schedule:
                type: "now"
              documents:
                - name: "John Doe"
                  email: "john@example.com"
                  contact_number: "1234567890"
                  alternate_number: "+919876543210"
                  occupation: "Sales Manager"
                  company_name: "Acme Corp"
                  address: "123 Main St, Mumbai"
                  about: "Warm lead from webinar"
                  context: "Follow up on proposal sent last week"
                  labels: ["warm-lead", "webinar"]
                  title: "Q1 Outreach"
                  description: "Lead from Q1 campaign"
                  document_id: "6902fb5840a736e125e80ebc"
      responses:
        '200':
          description: Task created successfully
          content:
            application/json:
              example:
                status_code: 200
                message: "Task Created Successfully"
                data:
                  run_id: "R74802366fe9011f0878d43cd8a99e069"
                  task_ids:
                    - "T74802367fe9011f0878d43cd8a99e069"
                  status: "pending"
        '206':
          description: Partial Content - Business logic error
          content:
            application/json:
              example:
                message: "Task creation failed"
                errors: "Detailed error description"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Runs ─────────────────────────────────────────────────────
  /api/v2/platform/spaces/{space_token}/runs/:
    get:
      operationId: getAllRuns
      summary: Get All Runs in a Space
      tags: [Runs]
      security:
        - TokenAuth: []
      parameters:
        - name: space_token
          in: path
          required: true
          schema:
            type: string
            example: "8KZAMRAHSXXXXXXMAYNASMJC"
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of all runs
          content:
            application/json:
              example:
                count: 9
                status_code: 200
                message: "Runs Fetched Successfully"
                data:
                  - run_id: "Recac64fe03e911f1878d43cd8a99e069"
                    collection_ref: "collection_data_SA2W8R6NZRK6C9PO3JSL1J85"
                    run_mode: "prefect"
                    status: "completed"
                    created: "2026-02-07T05:57:45Z"
                    modified: "2026-02-07T05:57:45Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/spaces/{space_token}/runs/{run_id}/tasks/:
    get:
      operationId: getRunTasks
      summary: Get Detailed Tasks for a Specific Run
      tags: [Runs]
      security:
        - TokenAuth: []
      parameters:
        - name: space_token
          in: path
          required: true
          schema:
            type: string
            example: "8KZAMRAHSXXXXXXMAYNASMJC"
        - name: run_id
          in: path
          required: true
          schema:
            type: string
            example: "Recac64fe03e911f1878d43cd8a99e069"
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of tasks for the run
          content:
            application/json:
              example:
                count: 9
                status_code: 200
                message: "Tasks Fetched Successfully"
                data:
                  - _id: "697debb84c27faa892bfa0cc"
                    thread_id: "thread_9xkL2mQpR7vNwY4sZ3cJ8hF1"
                    user_info:
                      email: "user@example.com"
                      full_name: "John Doe"
                    task_id: "T8ff2ccdffe9a11f0878d43cd8a99e069"
                    run_id: "Recac64fe03e911f1878d43cd8a99e069"
                    task:
                      objective: "Call the lead and discuss the project requirements."
                    input:
                      name: "John Doe"
                      contact_number: "1234567890"
                      email: "john@example.com"
                      context: "Follow up on proposal"
                    output:
                      call_id: "CALL_7dAb3kR9mXvQ2pLw"
                      start_time: "2026-02-07T05:57:45Z"
                      end_time: "2026-02-07T06:02:30Z"
                      duration: 285
                      recording_url: "https://cdn.unpod.dev/recordings/CALL_7dAb3kR9mXvQ2pLw.mp3"
                      transcript:
                        - role: "agent"
                          content: "Hello, this is an AI assistant. Am I speaking with John Doe?"
                        - role: "user"
                          content: "Yes, this is John."
                      post_call_data:
                        summary: "Agent connected with John Doe and discussed project requirements."
                        outcome: "interested"
                        sentiment: "positive"
                      call_type: "outbound"
                      call_status: "completed"
                    assignee: "space-agent-8qmk42nslp91wrh3dz7btxc4"
                    status: "completed"
                    execution_type: "call"
                    created: "2026-02-07T05:57:45Z"
                    modified: "2026-02-07T06:02:35Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Call Detail Records (CDR) ────────────────────────────────
  /api/v2/platform/cdr/:
    get:
      operationId: getCDRs
      summary: Get Call Detail Records
      tags: [Call Logs]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
        - name: page
          in: query
          schema:
            type: integer
            example: 1
        - name: page_size
          in: query
          schema:
            type: integer
            example: 20
        - name: call_type
          in: query
          schema:
            type: string
            enum: [inbound, outbound]
            example: outbound
        - name: call_status
          in: query
          schema:
            type: string
            enum: [completed, notConnected, failed]
            example: completed
      responses:
        '200':
          description: List of telephony CDRs
          content:
            application/json:
              example:
                count: 643
                status_code: 200
                message: "Call logs fetched successfully"
                data:
                  - id: 33364
                    call_status: "completed"
                    end_reason: "call.in-progress.sip-completed-call"
                    call_type: "outbound"
                    bridge:
                      id: 12
                      name: "Acme Primary Bridge"
                    creation_time: "2025-11-08T05:32:29Z"
                    start_time: "2025-11-08T05:29:43Z"
                    end_time: "2025-11-08T05:32:28.686639Z"
                    call_duration: 165.686639
                    source_number: "+15551234567"
                    destination_number: "+15559876543"
                    failure_source: null
                    sip_cause: null
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Analytics ────────────────────────────────────────────────
  /api/v2/platform/organisation/analytics/:
    get:
      operationId: getAnalytics
      summary: Get Analytics
      tags: [Analytics]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
        - name: space_token
          in: query
          schema:
            type: string
            example: "8KZAMRAHSXXXXXXMAYNASMJC"
      responses:
        '200':
          description: Analytics data
          content:
            application/json:
              example:
                status_code: 200
                message: "Analytics fetched successfully"
                data:
                  total_calls: 1250
                  completed_calls: 987
                  failed_calls: 143
                  avg_duration_seconds: 145.3
                  total_tasks: 1350
                  task_status_breakdown:
                    pending: 45
                    running: 12
                    completed: 1180
                    failed: 113
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Providers ────────────────────────────────────────────────
  /api/v2/platform/telephony/providers/:
    get:
      operationId: getTelephonyProviders
      summary: Get Telephony Providers
      tags: [Providers]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of available telephony providers
          content:
            application/json:
              example:
                status_code: 200
                message: "Voice infra providers fetched successfully."
                data:
                  - id: 1
                    name: "Twilio"
                    slug: "twilio"
                  - id: 2
                    name: "Plivo"
                    slug: "plivo"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/providers-configurations/:
    get:
      operationId: getAllProviders
      summary: Get All Providers
      tags: [Providers]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of provider configurations
          content:
            application/json:
              example:
                status_code: 200
                message: "Provider configurations fetched successfully."
                data:
                  - id: 42
                    provider: "twilio"
                    account_sid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                    created_at: "2026-02-07T10:00:00Z"
                  - id: 43
                    provider: "plivo"
                    account_sid: "MAyyyyyyyyyyyyyyyyyyyyyyy"
                    created_at: "2026-02-10T09:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createProvider
      summary: Create Provider
      tags: [Providers]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - provider
                - account_sid
                - auth_token
              properties:
                provider:
                  type: string
                  example: "twilio"
                account_sid:
                  type: string
                  example: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                auth_token:
                  type: string
                  example: "your_auth_token_here"
            example:
              provider: "twilio"
              account_sid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
              auth_token: "your_auth_token_here"
      responses:
        '201':
          description: Provider created
          content:
            application/json:
              example:
                status_code: 201
                message: "Provider configuration created successfully."
                data:
                  id: 42
                  provider: "twilio"
                  account_sid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                  created_at: "2026-02-07T10:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/providers-configurations/{id}/:
    get:
      operationId: getProviderById
      summary: Get Provider by ID
      tags: [Providers]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 42
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: Provider configuration details
          content:
            application/json:
              example:
                status_code: 200
                message: "Provider configuration fetched successfully."
                data:
                  id: 42
                  provider: "twilio"
                  account_sid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                  created_at: "2026-02-07T10:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      operationId: updateProvider
      summary: Update Provider
      tags: [Providers]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 42
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                auth_token:
                  type: string
                  example: "your_new_auth_token_here"
                account_sid:
                  type: string
                  example: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            example:
              auth_token: "your_new_auth_token_here"
      responses:
        '200':
          description: Provider updated
          content:
            application/json:
              example:
                status_code: 200
                message: "Provider configuration updated successfully."
                data:
                  id: 42
                  provider: "twilio"
                  account_sid: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                  created_at: "2026-02-07T10:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: deleteProvider
      summary: Delete Provider
      tags: [Providers]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 42
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '204':
          description: Provider deleted successfully - No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─── Telephony Numbers ────────────────────────────────────────
  /api/v2/platform/telephony/numbers/:
    get:
      operationId: getTelephonyNumbers
      summary: Get Telephony Numbers
      tags: [Bridges]
      description: >
        List telephony numbers. With Org-Handle: org's own numbers (any state) + pool
        (NOT_ASSIGNED). Without Org-Handle: pool numbers only.
      security:
        - TokenAuth: []
      parameters:
        - name: Org-Handle
          in: header
          required: false
          schema:
            type: string
          description: "Organization domain handle. Omit to list pool numbers only."
      responses:
        '200':
          description: List of telephony numbers
          content:
            application/json:
              example:
                status_code: 200
                message: "Telephony numbers fetched successfully."
                data:
                  - id: 501
                    number: "+15551234567"
                    state: "ASSIGNED"
                    active: true
                  - id: 502
                    number: "+15559876543"
                    state: "NOT_ASSIGNED"
                    active: true
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Bridges ──────────────────────────────────────────────────
  /api/v2/platform/telephony/bridges/:
    get:
      operationId: getAllBridges
      summary: Get All Bridges
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of all bridges
          content:
            application/json:
              example:
                count: 6
                status_code: 200
                message: "Bridges fetched successfully."
                data:
                  - id: 314
                    name: "Sales Bridge"
                    slug: "sales-bridge-001"
                    provider: "twilio"
                    numbers:
                      - id: 501
                        number: "+1234567890"
                        status: "active"
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createBridge
      summary: Create Bridge
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - slug
              properties:
                name:
                  type: string
                  example: "Sales Bridge"
                slug:
                  type: string
                  example: "sales-bridge-001"
            example:
              name: "Sales Bridge"
              slug: "sales-bridge-001"
      responses:
        '201':
          description: Bridge created
          content:
            application/json:
              example:
                status_code: 201
                message: "Bridge created successfully."
                data:
                  id: 314
                  name: "Sales Bridge"
                  slug: "sales-bridge-001"
                  created_at: "2026-02-07T10:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/bridges/{slug}/:
    get:
      operationId: getBridgeBySlug
      summary: Get Bridge by Slug
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            example: "sales-bridge-001"
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: Bridge details
          content:
            application/json:
              example:
                status_code: 200
                message: "Bridge fetched successfully."
                data:
                  id: 314
                  name: "Sales Bridge"
                  slug: "sales-bridge-001"
                  numbers:
                    - id: 501
                      number_id: 25
                      number: "+1234567890"
                      state: "ASSIGNED"
                      active: true
                      channels_count: 2
                      status: "active"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      operationId: updateBridge
      summary: Update Bridge
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            example: "sales-bridge-001"
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "Sales Bridge — Updated"
            example:
              name: "Sales Bridge — Updated"
      responses:
        '200':
          description: Bridge updated
          content:
            application/json:
              example:
                status_code: 200
                message: "Bridge updated successfully."
                data:
                  id: 314
                  name: "Sales Bridge — Updated"
                  slug: "sales-bridge-001"
                  numbers:
                    - id: 501
                      number: "+1234567890"
                      status: "active"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: deleteBridge
      summary: Delete Bridge
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            example: "sales-bridge-001"
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '204':
          description: Bridge deleted successfully - No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v2/platform/telephony/bridges/{slug}/connect-provider/:
    post:
      operationId: connectProviderToBridge
      summary: Connect Provider to Bridge
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            example: "sales-bridge-001"
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - provider_config_id
                - phone_number
              properties:
                provider_config_id:
                  type: integer
                  example: 42
                phone_number:
                  type: string
                  example: "+1234567890"
            example:
              provider_config_id: 42
              phone_number: "+1234567890"
      responses:
        '200':
          description: Provider connected to bridge
          content:
            application/json:
              example:
                status_code: 200
                message: "Provider connected to bridge successfully."
                data:
                  message: "Provider connected successfully"
                  bridge_slug: "sales-bridge-001"
                  phone_number: "+1234567890"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/bridges/{slug}/disconnect-provider/:
    post:
      operationId: disconnectProviderFromBridge
      summary: Disconnect Provider from Bridge
      tags: [Bridges]
      security:
        - TokenAuth: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            example: "sales-bridge-001"
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - phone_number
              properties:
                phone_number:
                  type: string
                  example: "+1234567890"
            example:
              phone_number: "+1234567890"
      responses:
        '200':
          description: Provider disconnected from bridge
          content:
            application/json:
              example:
                status_code: 200
                message: "Provider disconnected from bridge successfully."
                data:
                  message: "Provider disconnected successfully"
                  bridge_slug: "sales-bridge-001"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Telephony ────────────────────────────────────────────────
  /api/v2/platform/telephony/overview/:
    get:
      operationId: getTelephonyOverview
      summary: Telephony Overview
      tags: [Telephony]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
        - $ref: '#/components/parameters/ProductId'
      responses:
        '200':
          description: Per-number lifecycle overview for the organization
          content:
            application/json:
              example:
                status_code: 200
                message: "Telephony overview fetched successfully."
                data:
                  - number_id: 501
                    number: "+15551234567"
                    bridge_slug: "support-bridge"
                    connection_state: "LINKED"
                    termination_kind: "sip"
                    agent_id: null
                    sync_state: "synced"
                    sync_detail: null
        '206':
          description: Business logic error (missing header / org not found)
          content:
            application/json:
              example:
                message: "Please provide Org-Handle in headers"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Trunks ───────────────────────────────────────────────────
  /api/v2/platform/telephony/trunks/:
    get:
      operationId: listTrunks
      summary: List Trunks
      tags: [Trunks]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: List of the organization's SIP trunks (secrets masked)
          content:
            application/json:
              example:
                status_code: 200
                message: "Trunks fetched successfully."
                data:
                  - id: 12
                    name: "Acme Primary Trunk"
                    sip_url: "sip:sip.acme-voice.com"
                    transport: "tcp"
                    port: "5060"
                    auth_username: null
                    auth_password: null
                    allowed_ips: ""
                    active: true
                    org_handle: "acme.co"
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createTrunk
      summary: Create Trunk
      tags: [Trunks]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - sip_url
              properties:
                name:
                  type: string
                  example: "My Carrier Trunk"
                sip_url:
                  type: string
                  description: "Carrier SIP URL / host."
                  example: "sip:carrier.net"
                auth_username:
                  type: string
                  example: "user"
                auth_password:
                  type: string
                  example: "pass"
                transport:
                  type: string
                  enum: [tcp, udp, tls]
                  default: tcp
                  example: "tcp"
                port:
                  type: string
                  default: "5060"
                  example: "5060"
                source_ips:
                  type: array
                  description: "Carrier source IP allow-list (CIDR allowed)."
                  items:
                    type: string
                  example: ["1.2.3.4", "5.6.7.0/24"]
            example:
              name: "My Carrier Trunk"
              sip_url: "sip:carrier.net"
              auth_username: "user"
              auth_password: "pass"
              transport: "tcp"
              port: "5060"
              source_ips: ["1.2.3.4", "5.6.7.0/24"]
      responses:
        '201':
          description: Trunk created (auth_password masked in response)
          content:
            application/json:
              example:
                status_code: 201
                message: "Trunk created successfully."
                data:
                  id: 21
                  name: "My Carrier Trunk"
                  sip_url: "sip:carrier.net"
                  transport: "tcp"
                  port: "5060"
                  auth_username: "user"
                  auth_password: "pass"
                  allowed_ips: "1.2.3.4,5.6.7.0/24"
                  active: true
                  org_handle: "acme.co"
        '400':
          description: Invalid trunk payload
          content:
            application/json:
              example:
                status_code: 400
                message: "Invalid trunk payload"
                error:
                  sip_url: ["This field is required."]
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/trunks/{id}/:
    get:
      operationId: getTrunk
      summary: Get Trunk
      tags: [Trunks]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 12
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '200':
          description: Trunk detail (secrets masked)
          content:
            application/json:
              example:
                status_code: 200
                message: "Trunk fetched successfully."
                data:
                  id: 12
                  name: "Acme Primary Trunk"
                  sip_url: "sip:sip.acme-voice.com"
                  transport: "tcp"
                  port: "5060"
                  auth_username: null
                  auth_password: null
                  allowed_ips: ""
                  active: true
                  org_handle: "acme.co"
        '404':
          description: Trunk not found
          content:
            application/json:
              example:
                status_code: 404
                message: "Trunk not found."
        '401':
          $ref: '#/components/responses/Unauthorized'

    delete:
      operationId: deleteTrunk
      summary: Delete Trunk
      tags: [Trunks]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 21
        - $ref: '#/components/parameters/OrgHandle'
      responses:
        '204':
          description: Trunk deleted successfully
        '404':
          description: Trunk not found
          content:
            application/json:
              example:
                status_code: 404
                message: "Trunk not found."
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/trunks/{id}/attach-numbers/:
    post:
      operationId: attachNumbersToTrunk
      summary: Attach Numbers to Trunk
      tags: [Trunks]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 21
        - $ref: '#/components/parameters/OrgHandle'
        - $ref: '#/components/parameters/ProductId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - number_ids
              properties:
                number_ids:
                  type: array
                  items:
                    type: integer
                  example: [501]
                bridge_slug:
                  type: string
                  description: "Optional explicit bridge; auto-resolved when omitted."
                region:
                  type: string
                  example: "IN"
            example:
              number_ids: [501]
      responses:
        '201':
          description: Numbers mapped to trunk; origin endpoint returned
          content:
            application/json:
              example:
                status_code: 201
                message: "Numbers mapped to trunk."
                data:
                  trunk_id: 21
                  origin_endpoint:
                    ingress: "sip:sip.unpod.tel"
                    dids: ["+15551234567"]
                    accepted_source_ips: ["1.2.3.4", "5.6.7.0/24"]
                    region: "us-east"
                  numbers:
                    - number_id: 501
                      number: "+15551234567"
                      connection_state: "NOT_LINKED"
                      ok: true
        '400':
          description: No numbers could be mapped
          content:
            application/json:
              example:
                status_code: 400
                message: "No numbers could be mapped."
                data:
                  trunk_id: 21
                  origin_endpoint:
                    ingress: "sip:sip.unpod.tel"
                    dids: []
                    accepted_source_ips: ["1.2.3.4", "5.6.7.0/24"]
                    region: null
                  numbers:
                    - number_id: 501
                      ok: false
                      error: "Number not found or not available to this organization."
        '404':
          description: Trunk not found
          content:
            application/json:
              example:
                status_code: 404
                message: "Trunk not found."
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v2/platform/telephony/trunks/{id}/detach-numbers/:
    post:
      operationId: detachNumbersFromTrunk
      summary: Detach Numbers from Trunk
      tags: [Trunks]
      security:
        - TokenAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 21
        - $ref: '#/components/parameters/OrgHandle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - number_ids
              properties:
                number_ids:
                  type: array
                  items:
                    type: integer
                  example: [501]
            example:
              number_ids: [501]
      responses:
        '200':
          description: Numbers unmapped from trunk (partial-success per number)
          content:
            application/json:
              example:
                status_code: 200
                message: "Numbers unmapped from trunk."
                data:
                  trunk_id: 21
                  numbers:
                    - number_id: 501
                      ok: true
        '404':
          description: Trunk not found
          content:
            application/json:
              example:
                status_code: 404
                message: "Trunk not found."
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─── Billing ──────────────────────────────────────────────────
  /api/v2/platform/organisation/billing/:
    get:
      operationId: getBilling
      summary: Get Billing Data
      tags: [Billing]
      security:
        - TokenAuth: []
      parameters:
        - $ref: '#/components/parameters/OrgHandle'
        - name: page
          in: query
          schema:
            type: integer
            example: 1
        - name: page_size
          in: query
          schema:
            type: integer
            example: 20
      responses:
        '200':
          description: Billing data
          content:
            application/json:
              example:
                count: 12
                status_code: 200
                message: "Billing data fetched successfully"
                results:
                  - id: 101
                    amount: "299.00"
                    currency: "USD"
                    status: "paid"
                    invoice_date: "2026-02-01T00:00:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    TokenAuth:
      type: apiKey
      in: header
      name: Authorization
      description: "Format: Token <your-api-key>"

  parameters:
    OrgHandle:
      name: Org-Handle
      in: header
      required: true
      schema:
        type: string
        example: "unpod.tv"
      description: "Organization domain handle"
    ProductId:
      name: Product-Id
      in: header
      required: false
      schema:
        type: string
        example: "unpod.dev"
      description: "Product scope for the auto-resolved bridge (default unpod.dev)"

  responses:
    Unauthorized:
      description: Unauthorized - Invalid or missing API token
      content:
        application/json:
          example:
            status_code: 401
            message: "Authentication credentials were not provided."
    NotFound:
      description: Resource not found
      content:
        application/json:
          example:
            status_code: 404
            message: "Not found."
