openapi: 3.1.0
info:
  title: AbduPbx Integration API
  version: 1.0.0
  summary: Company-scoped PBX calls, operators, and stereo recordings API
  description: |
    AbduPbx Integration API lets an authorized company retrieve its call history,
    provision operators, download temporary two-channel recordings, and acknowledge
    a stereo recording after it has been processed.

    Every integration access token belongs to exactly one company. Integration
    requests do not accept `company_id`; the tenant is derived from the bearer token.

    When stereo recording is enabled for a company, AbduPbx keeps both recordings:

    * the existing mono recording, which is retained;
    * a temporary stereo recording, where the left channel is caller/read audio and
      the right channel is callee/transmit audio.

    Stereo audio is deleted when its configured retention period expires, when the
    acknowledge endpoint is called, or when the stereo resource is deleted directly.
    Deleting stereo audio never deletes the mono recording or the call history.
  contact:
    name: AbduPbx Integration Support
    url: https://salehouse-pbx.uz
servers:
  - url: https://api.salehouse-pbx.uz/api/v1
    description: Production
tags:
  - name: Authentication
    description: Obtain a bearer token.
  - name: Calls
    description: Read tenant-scoped call history.
  - name: Operators
    description: Provision PBX operators and their extensions.
  - name: Stereo recordings
    description: Download or delete temporary two-channel audio.
security:
  - bearerAuth: []
paths:
  /auth/login:
    post:
      tags:
        - Authentication
      operationId: login
      summary: Log in and obtain a bearer token
      description: |
        Exchange an authorized company administrator's login and password for a
        Sanctum bearer token. Integration endpoints require a company administrator
        account. Send the returned token in the `Authorization` header on every
        integration request.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
            examples:
              integrationLogin:
                summary: Company administrator login
                value:
                  login: acme_admin
                  password: change-this-password
      responses:
        "200":
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoginResponse"
              examples:
                success:
                  value:
                    success: true
                    data:
                      token: 1|plain-text-token-returned-once
                      user:
                        id: 91
                        company_id: 17
                        name: ACME Administrator
                        login: acme_admin
                        role: company_admin
                        status: true
                        company:
                          id: 17
                          name: ACME
                          stereo_recording_enabled: true
                          stereo_recording_retention_hours: 24
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/TooManyRequests"

  /integration/calls:
    get:
      tags:
        - Calls
      operationId: listIntegrationCalls
      summary: List call history
      description: |
        Returns only calls belonging to the bearer token's company. Results are
        ordered from newest to oldest. `call_id` is the numeric AbduPbx API ID;
        `uuid` is the original Asterisk call ID.
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/Direction"
        - $ref: "#/components/parameters/Disposition"
        - $ref: "#/components/parameters/OperatorId"
        - $ref: "#/components/parameters/ExtensionId"
        - $ref: "#/components/parameters/DateFrom"
        - $ref: "#/components/parameters/DateTo"
        - $ref: "#/components/parameters/Sort"
        - $ref: "#/components/parameters/Order"
      responses:
        "200":
          description: Paginated calls
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallListResponse"
              examples:
                calls:
                  $ref: "#/components/examples/CallList"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/TooManyRequests"

  /integration/calls/{call_id}:
    get:
      tags:
        - Calls
      operationId: showIntegrationCall
      summary: Get one call
      description: |
        Returns a single call owned by the authenticated company. A call owned by a
        different company is returned as `404`, not `403`, to avoid tenant discovery.
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Call details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallResponse"
              examples:
                answeredCall:
                  value:
                    success: true
                    data:
                      call_id: 8421
                      uuid: "1784022000.128"
                      direction: inbound
                      caller: "998901234567"
                      callee: "102"
                      started_at: "2026-07-28T08:20:00Z"
                      answered_at: "2026-07-28T08:20:08Z"
                      ended_at: "2026-07-28T08:20:37Z"
                      duration_seconds: 37
                      bill_seconds: 29
                      disposition: answered
                      hangup_cause: NORMAL_CLEARING
                      operator:
                        id: 344
                        name: Operator 102
                        login: operator102
                      extension:
                        id: 501
                        number: "102"
                        name: Operator 102
                      mono_recording:
                        available: true
                        download_url: https://api.salehouse-pbx.uz/calls-records/download/token/rec.mp3
                        duration_seconds: 29
                        size_bytes: 468302
                      stereo_recording:
                        available: true
                        download_url: https://api.salehouse-pbx.uz/api/v1/integration/calls/8421/stereo-recording
                        expires_at: "2026-07-29T08:20:37Z"
                        deleted_at: null
                        deletion_reason: null
                        duration_seconds: 29
                        size_bytes: 936604
                        channels:
                          left: caller
                          right: callee
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"

  /integration/operators:
    get:
      tags:
        - Operators
      operationId: listIntegrationOperators
      summary: List company operators and SIP extensions
      description: |
        Returns only operators belonging to the bearer token's company. SIP
        passwords are never returned by this endpoint.
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Search"
        - name: status
          in: query
          description: Filter active or inactive operators.
          schema:
            type: boolean
      responses:
        "200":
          description: Paginated operators
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperatorListResponse"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/TooManyRequests"
    post:
      tags:
        - Operators
      operationId: createIntegrationOperator
      summary: Create an operator and PBX extension
      description: |
        Creates the operator, extension, and SIP account as one operation. If
        `sip_password` is omitted, AbduPbx generates one. The SIP password is
        returned only in this response and must be stored securely.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OperatorCreateRequest"
            examples:
              operator:
                value:
                  name: Dilshod Karimov
                  password: strong-panel-password
                  phone: "+998901234567"
                  email: dilshod@example.uz
                  extension_number: "102"
                  can_record: true
                  webrtc_enabled: true
                  transport: udp
                  status: true
      responses:
        "201":
          description: Operator provisioned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperatorCreateResponse"
              examples:
                created:
                  value:
                    success: true
                    data:
                      operator:
                        id: 344
                        name: Dilshod Karimov
                        login: dilshod_102
                        phone: "+998901234567"
                        email: dilshod@example.uz
                        temporary_password: strong-panel-password
                      extension:
                        id: 501
                        number: "102"
                        display_name: Dilshod Karimov
                        can_record: true
                      sip:
                        server: acme.sip.salehouse-pbx.uz
                        username: "102"
                        auth_username: "102"
                        password: generated-or-requested-sip-password
                        transport: udp
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/TooManyRequests"

  /integration/calls/{call_id}/stereo-recording:
    get:
      tags:
        - Stereo recordings
      operationId: downloadStereoRecording
      summary: Download a stereo recording
      description: |
        Streams the temporary stereo MP3 for this call. Send the same bearer token
        used for the call API. The left channel is caller/read audio and the right
        channel is callee/transmit audio.

        A recording is unavailable after it expires or is acknowledged. The mono
        recording and call record remain available according to their own policy.
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Two-channel MP3 audio
          headers:
            Content-Disposition:
              description: Inline stereo recording file name
              schema:
                type: string
                example: inline; filename="call-8421-stereo.mp3"
            X-Call-Id:
              description: Numeric AbduPbx call ID
              schema:
                type: integer
                format: int64
                example: 8421
            X-Stereo-Expires-At:
              description: Stereo retention deadline
              schema:
                type: string
                format: date-time
                example: "2026-07-29T08:20:37Z"
          content:
            audio/mpeg:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/StereoNotAvailable"
        "410":
          $ref: "#/components/responses/StereoExpired"
        "429":
          $ref: "#/components/responses/TooManyRequests"
    delete:
      tags:
        - Stereo recordings
      operationId: deleteStereoRecording
      summary: Delete a stereo recording immediately
      description: |
        Deletes only the temporary stereo file. The call history and mono recording
        are preserved. The operation is idempotent: repeating it for an already
        deleted recording still succeeds. A call owned by another company is `404`.
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Stereo recording deleted, already deleted, or not available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StereoAcknowledgeResponse"
              examples:
                deleted:
                  $ref: "#/components/examples/StereoDeleted"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"

  /integration/stereo-recordings/acknowledge:
    post:
      tags:
        - Stereo recordings
      operationId: acknowledgeStereoRecording
      summary: Acknowledge processing and delete stereo audio
      description: |
        Call this endpoint after the integrated system has safely received and
        processed the stereo audio. AbduPbx records the acknowledgement and deletes
        only the stereo file. This endpoint is idempotent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StereoAcknowledgeRequest"
            examples:
              acknowledge:
                value:
                  call_id: 8421
      responses:
        "200":
          description: Acknowledged
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StereoAcknowledgeResponse"
              examples:
                acknowledged:
                  $ref: "#/components/examples/StereoDeleted"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/TooManyRequests"

webhooks:
  callEnd:
    post:
      operationId: receiveCallEndWebhook
      summary: Receive a completed-call event from AbduPbx
      description: |
        This is an outbound AbduPbx webhook: the URL is hosted by the integrated
        customer, not by AbduPbx. AbduPbx signs the exact raw JSON body with the
        webhook secret:

        `X-SaleHouse-Signature: sha256=<HMAC-SHA256(raw_body, secret)>`

        Verify the signature before parsing or reformatting JSON. Store
        `X-SaleHouse-Delivery` as a unique idempotency key, process by `call_id`,
        enqueue long-running work, and return a `2xx` response promptly.
      security: []
      parameters:
        - name: X-SaleHouse-Event
          in: header
          required: true
          description: Always `call_end` for this webhook.
          schema:
            type: string
            const: call_end
        - name: X-SaleHouse-Delivery
          in: header
          required: true
          description: Unique delivery UUID. Use it to reject duplicate processing.
          schema:
            type: string
            format: uuid
        - name: X-SaleHouse-Signature
          in: header
          required: true
          description: HMAC-SHA256 signature of the exact raw request body.
          schema:
            type: string
            pattern: "^sha256=[a-f0-9]{64}$"
            example: sha256=bd332a59b83ee271d24a57867580ba4b89efd460481983d5aa439390078ef9f8
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallEndWebhook"
            examples:
              answeredWithStereo:
                $ref: "#/components/examples/CallEndWebhook"
      responses:
        "200":
          description: Processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "202":
          description: Accepted for asynchronous processing
        "204":
          description: Accepted without a response body

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Laravel Sanctum token
      description: "Use `Authorization: Bearer <token>`."

  parameters:
    CallId:
      name: call_id
      in: path
      required: true
      description: Numeric AbduPbx call ID. This is not the Asterisk `uuid`.
      schema:
        type: integer
        format: int64
        minimum: 1
        example: 8421
    Page:
      name: page
      in: query
      description: Page number.
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      description: Items per page.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 15
    Search:
      name: search
      in: query
      description: Search caller, callee, or Asterisk UUID.
      schema:
        type: string
        maxLength: 120
        example: "99890"
    Direction:
      name: direction
      in: query
      description: Filter by call direction.
      schema:
        $ref: "#/components/schemas/CallDirection"
    Disposition:
      name: disposition
      in: query
      description: Filter by final call disposition.
      schema:
        $ref: "#/components/schemas/CallDisposition"
    OperatorId:
      name: operator_id
      in: query
      description: Filter by a company operator.
      schema:
        type: integer
        format: int64
        minimum: 1
    ExtensionId:
      name: extension_id
      in: query
      description: Filter by a company extension.
      schema:
        type: integer
        format: int64
        minimum: 1
    DateFrom:
      name: date_from
      in: query
      description: Include calls started on or after this date.
      schema:
        type: string
        format: date
        example: "2026-07-01"
    DateTo:
      name: date_to
      in: query
      description: Include calls started on or before this date.
      schema:
        type: string
        format: date
        example: "2026-07-31"
    Sort:
      name: sort
      in: query
      description: Field used to sort results.
      schema:
        type: string
        enum:
          - id
          - started_at
          - duration_seconds
          - bill_seconds
          - disposition
        default: started_at
    Order:
      name: order
      in: query
      description: Sort direction.
      schema:
        type: string
        enum:
          - asc
          - desc
        default: desc

  responses:
    Unauthenticated:
      description: Bearer token is missing, invalid, expired, or revoked.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            message: Unauthenticated.
    Forbidden:
      description: The authenticated account does not have company administrator access.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            message: This action is unauthorized.
    NotFound:
      description: Resource not found for the authenticated company.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            message: Resource not found.
    StereoNotAvailable:
      description: Call not found, stereo was not recorded, or stereo is no longer available.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            message: Stereo recording is not available.
    StereoExpired:
      description: The stereo retention deadline passed; the temporary file was deleted.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            message: Stereo yozuv saqlash muddati tugagan va o‘chirildi.
    ValidationError:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"
          example:
            message: The given data was invalid.
            errors:
              login:
                - Login yoki parol noto‘g‘ri.
    TooManyRequests:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          description: Seconds until another request should be attempted.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            message: Too Many Attempts.

  schemas:
    LoginRequest:
      type: object
      additionalProperties: false
      required:
        - login
        - password
      properties:
        login:
          type: string
          maxLength: 120
          example: acme_admin
        password:
          type: string
          format: password
          maxLength: 120
          writeOnly: true

    LoginResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required:
            - token
            - user
          properties:
            token:
              type: string
              description: Plain-text bearer token. Store it securely.
              example: 1|plain-text-token-returned-once
            user:
              $ref: "#/components/schemas/IntegrationUser"

    IntegrationUser:
      type: object
      additionalProperties: true
      required:
        - id
        - company_id
        - name
        - login
        - role
        - status
      properties:
        id:
          type: integer
          format: int64
        company_id:
          type: integer
          format: int64
        name:
          type: string
        login:
          type: string
        role:
          type: string
          example: company_admin
        status:
          type: boolean
        company:
          $ref: "#/components/schemas/Company"

    Company:
      type: object
      additionalProperties: true
      required:
        - id
        - name
        - stereo_recording_enabled
        - stereo_recording_retention_hours
      properties:
        id:
          type: integer
          format: int64
          example: 17
        name:
          type: string
          example: ACME
        stereo_recording_enabled:
          type: boolean
          description: Set by a super administrator for this company.
          default: false
          example: true
        stereo_recording_retention_hours:
          type: integer
          minimum: 1
          maximum: 720
          default: 24
          description: Number of hours a newly created stereo recording may remain.
          example: 24

    CallDirection:
      type: string
      enum:
        - inbound
        - outbound
        - internal

    CallDisposition:
      type: string
      maxLength: 40
      description: Normalized PBX disposition, for example `answered`, `no_answer`, `busy`, or `failed`.
      example: answered

    Call:
      type: object
      additionalProperties: false
      required:
        - call_id
        - uuid
        - direction
        - caller
        - callee
        - started_at
        - duration_seconds
        - bill_seconds
        - disposition
        - mono_recording
        - stereo_recording
      properties:
        call_id:
          type: integer
          format: int64
          description: Numeric AbduPbx call ID used by integration endpoints.
          example: 8421
        uuid:
          type: string
          description: Original Asterisk call ID.
          maxLength: 120
          example: "1784022000.128"
        direction:
          $ref: "#/components/schemas/CallDirection"
        caller:
          type: string
          example: "998901234567"
        callee:
          type: string
          example: "102"
        started_at:
          type: string
          format: date-time
        answered_at:
          type:
            - string
            - "null"
          format: date-time
        ended_at:
          type:
            - string
            - "null"
          format: date-time
        duration_seconds:
          type: integer
          minimum: 0
        bill_seconds:
          type: integer
          minimum: 0
        disposition:
          $ref: "#/components/schemas/CallDisposition"
        hangup_cause:
          type:
            - string
            - "null"
        operator:
          anyOf:
            - $ref: "#/components/schemas/CallOperator"
            - type: "null"
        extension:
          anyOf:
            - $ref: "#/components/schemas/CallExtension"
            - type: "null"
        mono_recording:
          anyOf:
            - $ref: "#/components/schemas/MonoRecording"
            - type: "null"
        stereo_recording:
          anyOf:
            - $ref: "#/components/schemas/StereoRecording"
            - type: "null"

    CallOperator:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - login
      properties:
        id:
          type: integer
          format: int64
          example: 344
        name:
          type: string
          example: Operator 102
        login:
          type:
            - string
            - "null"
          example: operator102

    CallExtension:
      type: object
      additionalProperties: false
      required:
        - id
        - number
        - name
      properties:
        id:
          type: integer
          format: int64
          example: 501
        number:
          type: string
          example: "102"
        name:
          type:
            - string
            - "null"
          example: Operator 102

    MonoRecording:
      type: object
      additionalProperties: false
      required:
        - available
      properties:
        available:
          type: boolean
        download_url:
          type:
            - string
            - "null"
          format: uri
        duration_seconds:
          type:
            - integer
            - "null"
          minimum: 0
        size_bytes:
          type:
            - integer
            - "null"
          minimum: 0
      description: Permanent mono recording metadata. Stereo deletion does not change it.

    StereoRecording:
      type: object
      additionalProperties: false
      required:
        - available
        - channels
      properties:
        available:
          type: boolean
        download_url:
          type:
            - string
            - "null"
          format: uri
          description: Bearer-protected stereo download URL.
        expires_at:
          type:
            - string
            - "null"
          format: date-time
          description: Retention deadline captured when the stereo recording is created.
        deleted_at:
          type:
            - string
            - "null"
          format: date-time
        deletion_reason:
          type:
            - string
            - "null"
        duration_seconds:
          type:
            - integer
            - "null"
          minimum: 0
        size_bytes:
          type:
            - integer
            - "null"
          minimum: 0
        channels:
          $ref: "#/components/schemas/StereoChannels"

    StereoChannels:
      type: object
      additionalProperties: false
      required:
        - left
        - right
      properties:
        left:
          type: string
          const: caller
          description: Caller audio captured from the Asterisk read/receive side.
        right:
          type: string
          const: callee
          description: Callee audio captured from the Asterisk transmit side.

    CallResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/Call"

    CallListResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - data
        - meta
      properties:
        success:
          type: boolean
          const: true
        data:
          type: array
          items:
            $ref: "#/components/schemas/Call"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    PaginationMeta:
      type: object
      additionalProperties: false
      required:
        - current_page
        - last_page
        - per_page
        - total
      properties:
        current_page:
          type: integer
          minimum: 1
          example: 1
        last_page:
          type: integer
          minimum: 1
          example: 12
        per_page:
          type: integer
          minimum: 1
          example: 15
        total:
          type: integer
          minimum: 0
          example: 174

    OperatorListResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - data
        - meta
      properties:
        success:
          type: boolean
          const: true
        data:
          type: array
          items:
            $ref: "#/components/schemas/IntegrationOperator"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    IntegrationOperator:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - login
        - phone
        - email
        - status
        - extensions
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        login:
          type: string
        phone:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
          format: email
        status:
          type: boolean
        extensions:
          type: array
          items:
            $ref: "#/components/schemas/IntegrationExtension"

    IntegrationExtension:
      type: object
      additionalProperties: false
      required:
        - id
        - number
        - display_name
        - can_record
        - webrtc_enabled
        - status
        - sip
      properties:
        id:
          type: integer
          format: int64
        number:
          type: string
        display_name:
          type:
            - string
            - "null"
        can_record:
          type: boolean
        webrtc_enabled:
          type: boolean
        status:
          type: boolean
        sip:
          oneOf:
            - $ref: "#/components/schemas/IntegrationSipAccount"
            - type: "null"

    IntegrationSipAccount:
      type: object
      additionalProperties: false
      required:
        - server
        - username
        - auth_username
        - transport
        - status
      properties:
        server:
          type: string
        username:
          type: string
        auth_username:
          type:
            - string
            - "null"
        transport:
          type: string
        status:
          type: boolean

    OperatorCreateRequest:
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 160
          example: Dilshod Karimov
        password:
          type:
            - string
            - "null"
          format: password
          minLength: 6
          maxLength: 120
          writeOnly: true
          description: Optional operator panel password; generated when omitted.
        phone:
          type:
            - string
            - "null"
          maxLength: 40
          example: "+998901234567"
        email:
          type:
            - string
            - "null"
          format: email
          maxLength: 120
        extension_number:
          type:
            - string
            - "null"
          maxLength: 20
          description: Omit to let AbduPbx select the next available extension.
          example: "102"
        sip_password:
          type:
            - string
            - "null"
          minLength: 6
          maxLength: 120
          writeOnly: true
          description: Omit to generate a secure SIP password.
        max_contacts:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 20
          default: 3
        can_record:
          type:
            - boolean
            - "null"
          default: true
          description: Allows mono recording. Stereo additionally requires the company flag.
        webrtc_enabled:
          type:
            - boolean
            - "null"
          default: false
        transport:
          type:
            - string
            - "null"
          enum:
            - udp
            - tcp
            - tls
            - ws
            - wss
            - null
          default: udp
        status:
          type:
            - boolean
            - "null"
          default: true

    OperatorCreateResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/ProvisionedOperator"

    ProvisionedOperator:
      type: object
      additionalProperties: false
      required:
        - operator
        - extension
        - sip
      properties:
        operator:
          type: object
          additionalProperties: false
          required:
            - id
            - name
            - login
            - phone
            - email
            - temporary_password
          properties:
            id:
              type: integer
              format: int64
            name:
              type: string
            login:
              type: string
              description: Auto-generated unique operator login.
            phone:
              type:
                - string
                - "null"
            email:
              type:
                - string
                - "null"
              format: email
            temporary_password:
              type: string
              writeOnly: true
              description: Returned in this creation response; deliver and store it securely.
        extension:
          type: object
          additionalProperties: false
          required:
            - id
            - number
            - display_name
            - can_record
          properties:
            id:
              type: integer
              format: int64
            number:
              type: string
            display_name:
              type:
                - string
                - "null"
            can_record:
              type: boolean
        sip:
          type: object
          additionalProperties: false
          required:
            - server
            - username
            - auth_username
            - password
            - transport
          properties:
            server:
              type:
                - string
                - "null"
              example: acme.sip.salehouse-pbx.uz
            username:
              type:
                - string
                - "null"
            auth_username:
              type:
                - string
                - "null"
            password:
              type: string
              writeOnly: true
              description: Returned once.
            transport:
              type:
                - string
                - "null"
              enum:
                - udp
                - tcp
                - tls
                - ws
                - wss
                - null

    StereoAcknowledgeRequest:
      type: object
      additionalProperties: false
      required:
        - call_id
      properties:
        call_id:
          type: integer
          format: int64
          minimum: 1
          example: 8421

    StereoAcknowledgeResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required:
            - call_id
            - stereo_deleted
            - status
            - deleted_at
          properties:
            call_id:
              type: integer
              format: int64
            stereo_deleted:
              type: boolean
              description: False only when this call never had a stereo recording.
            status:
              type: string
              enum:
                - deleted
                - already_deleted
                - not_available
            deleted_at:
              type:
                - string
                - "null"
              format: date-time

    CallEndWebhook:
      type: object
      additionalProperties: true
      required:
        - event
        - call_id
        - uuid
        - direction
        - caller
        - callee
        - date
        - call_duration
        - dialog_duration
        - hangup_cause
        - download_url
        - stereo_recording
      properties:
        event:
          type: string
          const: call_end
        call_id:
          type: integer
          format: int64
          example: 8421
        uuid:
          type: string
          description: Asterisk call ID.
          example: "1784022000.128"
        domain:
          type:
            - string
            - "null"
          example: acme.sip.salehouse-pbx.uz
        direction:
          $ref: "#/components/schemas/CallDirection"
        caller:
          type: string
          example: "998901234567"
        callee:
          type: string
          example: "102"
        origin:
          type: string
          example: sip
        from_domain:
          type:
            - string
            - "null"
        to_domain:
          type:
            - string
            - "null"
        gateway:
          type:
            - string
            - "null"
        date:
          type: string
          description: Unix timestamp in seconds, represented as a string.
          example: "1784022037"
        call_duration:
          type: string
          description: Total call duration in seconds.
          example: "37"
        dialog_duration:
          type: string
          description: Answered conversation duration in seconds.
          example: "29"
        hangup_cause:
          type: string
          example: NORMAL_CLEARING
        download_url:
          type:
            - string
            - "null"
          format: uri
          description: Existing mono recording URL.
        stereo_recording:
          anyOf:
            - $ref: "#/components/schemas/WebhookStereoRecording"
            - type: "null"
        quality_score:
          type: string
          example: "0"

    WebhookStereoRecording:
      type: object
      additionalProperties: false
      required:
        - download_url
        - expires_at
        - channels
      properties:
        download_url:
          type: string
          format: uri
          description: Bearer-protected stereo download URL.
        expires_at:
          type:
            - string
            - "null"
          format: date-time
        channels:
          $ref: "#/components/schemas/StereoChannels"

    ErrorResponse:
      type: object
      additionalProperties: true
      required:
        - message
      properties:
        success:
          type: boolean
          const: false
        message:
          type: string
        error_code:
          type:
            - string
            - "null"

    ValidationErrorResponse:
      allOf:
        - $ref: "#/components/schemas/ErrorResponse"
        - type: object
          properties:
            errors:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string

  examples:
    CallList:
      summary: One answered call
      value:
        success: true
        data:
          - call_id: 8421
            uuid: "1784022000.128"
            direction: inbound
            caller: "998901234567"
            callee: "102"
            started_at: "2026-07-28T08:20:00Z"
            answered_at: "2026-07-28T08:20:08Z"
            ended_at: "2026-07-28T08:20:37Z"
            duration_seconds: 37
            bill_seconds: 29
            disposition: answered
            hangup_cause: NORMAL_CLEARING
            operator:
              id: 344
              name: Operator 102
              login: operator102
            extension:
              id: 501
              number: "102"
              name: Operator 102
            mono_recording:
              available: true
              download_url: https://api.salehouse-pbx.uz/calls-records/download/token/rec.mp3
              duration_seconds: 29
              size_bytes: 468302
            stereo_recording:
              available: true
              download_url: https://api.salehouse-pbx.uz/api/v1/integration/calls/8421/stereo-recording
              expires_at: "2026-07-29T08:20:37Z"
              deleted_at: null
              deletion_reason: null
              duration_seconds: 29
              size_bytes: 936604
              channels:
                left: caller
                right: callee
        meta:
          current_page: 1
          last_page: 12
          per_page: 15
          total: 174
    CallEndWebhook:
      summary: Completed call with temporary stereo audio
      value:
        event: call_end
        call_id: 8421
        uuid: "1784022000.128"
        domain: acme.sip.salehouse-pbx.uz
        direction: inbound
        caller: "998901234567"
        callee: "102"
        origin: sip
        from_domain: acme.sip.salehouse-pbx.uz
        to_domain: acme.sip.salehouse-pbx.uz
        gateway: "781138877"
        date: "1784022037"
        call_duration: "37"
        dialog_duration: "29"
        hangup_cause: NORMAL_CLEARING
        download_url: https://api.salehouse-pbx.uz/calls-records/download/token/rec.mp3
        stereo_recording:
          download_url: https://api.salehouse-pbx.uz/api/v1/integration/calls/8421/stereo-recording
          expires_at: "2026-07-29T08:20:37Z"
          channels:
            left: caller
            right: callee
        quality_score: "0"
    StereoDeleted:
      summary: Stereo deleted; mono recording and call history remain
      value:
        success: true
        data:
          call_id: 8421
          stereo_deleted: true
          status: deleted
          deleted_at: "2026-07-28T08:28:15Z"
