openapi: "3.1.0"
info:
  title: Telephony MiniApp — Incoming Events
  description: >
    Incoming events from the external application (MiniApp → Acloud).
    The external application sends a POST request to the hook endpoint:
    POST /external/rest/telephony/hook/mini_app/{account_id}/{uuid}
    where {account_id} is the account ID, {uuid} is the unique integration identifier
    (returned in the telephony/create API response).
  version: "1.0.0"

webhooks:

  call.init:
    post:
      summary: Call Initialization
      description: >
        Sent by the external application when a new call appears (before connection).
        For inbound calls, Acloud returns a routing decision — which employee to redirect
        the call to. For outbound calls — acknowledgment of receipt.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - call.init
                payload:
                  $ref: '#/components/schemas/CallInitPayload'
            examples:
              call_init_inbound:
                summary: Inbound call
                value:
                  method: call.init
                  payload:
                    call_id: call_abc123
                    from: "+79990001234"
                    to: "101"
                    direction: in
                    timestamp: "2026-04-09T10:30:00Z"
              call_init_outbound:
                summary: Outbound call
                value:
                  method: call.init
                  payload:
                    call_id: call_def456
                    from: "101"
                    to: "+79995556677"
                    direction: out
                    timestamp: "2026-04-09T11:00:00Z"
      responses:
        "200":
          description: >
            Event processed successfully. For inbound calls, contains the routing decision.
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    $ref: '#/components/schemas/RoutingResult'
              examples:
                redirect:
                  summary: Redirect to employee
                  value:
                    result:
                      action: redirect
                      target:
                        sip: "101"
                pass:
                  summary: Pass without redirect
                  value:
                    result:
                      action: pass
        "400":
          description: Validation error (invalid format or missing required fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Integration deactivated or account expired.
            Error codes: `telephony_deactivated`, `account_expired`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "410":
          description: >
            Account or integration not found.
            Error codes: `account_not_found`, `integration_not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  call.start:
    post:
      summary: Call Connected
      description: >
        Sent by the external application when the call was answered
        and the conversation started.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - call.start
                payload:
                  $ref: '#/components/schemas/CallStartPayload'
            examples:
              call_start:
                summary: Call connected
                value:
                  method: call.start
                  payload:
                    call_id: call_abc123
                    from: "+79990001234"
                    to: "101"
                    direction: in
                    timestamp: "2026-04-09T10:30:05Z"
      responses:
        "200":
          description: Event processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
        "400":
          description: Validation error (invalid format or missing required fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Integration deactivated or account expired.
            Error codes: `telephony_deactivated`, `account_expired`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "410":
          description: >
            Account or integration not found.
            Error codes: `account_not_found`, `integration_not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  call.terminate:
    post:
      summary: Call Ended
      description: >
        Sent by the external application when a call ends.
        Contains the final status and call duration.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - call.terminate
                payload:
                  $ref: '#/components/schemas/CallTerminatePayload'
            examples:
              call_terminate_success:
                summary: Successfully completed call
                value:
                  method: call.terminate
                  payload:
                    call_id: call_abc123
                    from: "+79990001234"
                    to: "101"
                    direction: in
                    timestamp: "2026-04-09T10:35:00Z"
                    call_duration: 295
                    status: terminated
              call_terminate_missed:
                summary: Missed call
                value:
                  method: call.terminate
                  payload:
                    call_id: call_abc124
                    from: "+79991112233"
                    to: "102"
                    direction: in
                    timestamp: "2026-04-09T10:40:00Z"
                    call_duration: 0
                    status: missed
      responses:
        "200":
          description: Event processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
        "400":
          description: Validation error (invalid format or missing required fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Integration deactivated or account expired.
            Error codes: `telephony_deactivated`, `account_expired`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "410":
          description: >
            Account or integration not found.
            Error codes: `account_not_found`, `integration_not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  call.record:
    post:
      summary: Call Recording Info
      description: >
        Sent by the external application after the call ends.
        Reports whether a call recording was created.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - call.record
                payload:
                  $ref: '#/components/schemas/CallRecordPayload'
            examples:
              call_record_exists:
                summary: Recording created
                value:
                  method: call.record
                  payload:
                    call_id: call_abc123
                    from: "+79990001234"
                    to: "101"
                    direction: in
                    timestamp: "2026-04-09T10:35:05Z"
                    has_record: true
              call_record_none:
                summary: No recording
                value:
                  method: call.record
                  payload:
                    call_id: call_abc125
                    from: "101"
                    to: "+79997778899"
                    direction: out
                    timestamp: "2026-04-09T12:00:10Z"
                    has_record: false
      responses:
        "200":
          description: Event processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
        "400":
          description: Validation error (invalid format or missing required fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Integration deactivated or account expired.
            Error codes: `telephony_deactivated`, `account_expired`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "410":
          description: >
            Account or integration not found.
            Error codes: `account_not_found`, `integration_not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  call.info:
    post:
      summary: Call Status Update
      description: >
        Sent by the external application when the call state changes.
        May arrive in any order relative to other events —
        the system processes it idempotently.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - call.info
                payload:
                  $ref: '#/components/schemas/CallInfoPayload'
            examples:
              call_info_active:
                summary: Call active
                value:
                  method: call.info
                  payload:
                    call_id: call_abc123
                    from: "+79990001234"
                    to: "101"
                    direction: in
                    timestamp: "2026-04-09T10:30:10Z"
                    status: active
              call_info_terminated:
                summary: Call ended
                value:
                  method: call.info
                  payload:
                    call_id: call_abc123
                    from: "+79990001234"
                    to: "101"
                    direction: in
                    timestamp: "2026-04-09T10:35:10Z"
                    status: terminated
      responses:
        "200":
          description: Event processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
        "400":
          description: Validation error (invalid format or missing required fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Integration deactivated or account expired.
            Error codes: `telephony_deactivated`, `account_expired`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "410":
          description: >
            Account or integration not found.
            Error codes: `account_not_found`, `integration_not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  integration.deactivate:
    post:
      summary: Integration Deactivation
      description: >
        Sent by the external application to deactivate the telephony integration.
        After processing, the integration is set to inactive state (active = false).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - integration.deactivate
                payload:
                  $ref: '#/components/schemas/IntegrationDeactivatePayload'
            examples:
              deactivate:
                summary: Deactivate integration
                value:
                  method: integration.deactivate
                  payload: {}
      responses:
        "200":
          description: Integration deactivated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
        "410":
          description: >
            Integration not found.
            Error code: `integration_not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  schemas:

    IncomingEvent:
      type: object
      required:
        - method
        - payload
      properties:
        method:
          type: string
          enum:
            - call.init
            - call.start
            - call.terminate
            - call.record
            - call.info
            - integration.deactivate
          description: Event type
        payload:
          oneOf:
            - $ref: '#/components/schemas/CallInitPayload'
            - $ref: '#/components/schemas/CallStartPayload'
            - $ref: '#/components/schemas/CallTerminatePayload'
            - $ref: '#/components/schemas/CallRecordPayload'
            - $ref: '#/components/schemas/CallInfoPayload'
            - $ref: '#/components/schemas/IntegrationDeactivatePayload'
          discriminator:
            propertyName: method

    # ─── Shared base ──────────────────────────────────────────────────────────

    CallBasePayload:
      type: object
      description: Base fields present in all call events
      required:
        - call_id
        - from
        - to
        - direction
        - timestamp
      properties:
        call_id:
          type: string
          description: Unique call identifier in the external system
          example: call_abc123
        from:
          type: string
          description: >
            Phone number or internal extension (extension) of the call source.
            For inbound calls — caller's phone number; for outbound — employee extension.
          example: "+79990001234"
        to:
          type: string
          description: >
            Phone number or extension of the call recipient.
            For inbound calls — employee extension; for outbound — phone number.
          example: "101"
        direction:
          type: string
          enum:
            - in
            - out
          description: Call direction. `in` — inbound, `out` — outbound.
        timestamp:
          type: string
          description: Event date and time in ISO 8601 format
          example: "2026-04-09T10:30:00Z"

    # ─── call.init ────────────────────────────────────────────────────────────

    CallInitPayload:
      allOf:
        - $ref: '#/components/schemas/CallBasePayload'
      description: Payload for the call initialization event. Fields match the base set.

    # ─── call.start ───────────────────────────────────────────────────────────

    CallStartPayload:
      allOf:
        - $ref: '#/components/schemas/CallBasePayload'
      description: Payload for the call connected event. Fields match the base set.

    # ─── call.terminate ───────────────────────────────────────────────────────

    CallTerminatePayload:
      allOf:
        - $ref: '#/components/schemas/CallBasePayload'
        - type: object
          required:
            - call_duration
            - status
          properties:
            call_duration:
              type: integer
              description: Call duration in seconds
              example: 295
            status:
              type: string
              enum:
                - terminated
                - missed
              description: >
                Final call status.
                `terminated` — call was answered and ended normally;
                `missed` — call was not answered (missed).

    # ─── call.record ──────────────────────────────────────────────────────────

    CallRecordPayload:
      allOf:
        - $ref: '#/components/schemas/CallBasePayload'
        - type: object
          required:
            - has_record
          properties:
            has_record:
              type: boolean
              description: Flag indicating whether a call recording exists
              example: true
            record_url:
              type: string
              description: Download link for the call recording
              example: https://domain/call_id
              nullable: true

    # ─── call.info ────────────────────────────────────────────────────────────

    CallInfoPayload:
      allOf:
        - $ref: '#/components/schemas/CallBasePayload'
        - type: object
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - active
                - terminated
                - missed
              description: >
                Current call status.
                `active` — call is active (connected);
                `terminated` — call ended successfully;
                `missed` — call was not answered.

    # ─── integration.deactivate ─────────────────────────────────────────────

    IntegrationDeactivatePayload:
      type: object
      description: >
        Integration deactivation event payload. Contains no fields —
        the act of sending the event signals a deactivation request.

    # ─── Routing result ───────────────────────────────────────────────────────

    RoutingResult:
      type: object
      required:
        - action
      properties:
        action:
          type: string
          enum:
            - redirect
            - pass
          description: >
            Routing action for an inbound call.
            `redirect` — redirect to a specific employee;
            `pass` — pass without changing the route.
        target:
          type: object
          description: "Redirect target. Present only when action: redirect."
          required:
            - sip
          properties:
            sip:
              type: string
              description: Employee SIP address or extension for redirect
              example: "101"

    # ─── Error response ───────────────────────────────────────────────────────

    ErrorResponse:
      type: object
      required:
        - result
        - code
        - message
      properties:
        result:
          type: string
          enum:
            - error
        code:
          type: integer
          description: HTTP response code
          example: 400
        error:
          type: string
          enum:
            - invalid_json
            - validation_error
            - event_not_supported
            - internal_error
            - account_not_found
            - integration_not_found
            - telephony_deactivated
            - account_expired
          description: >
            Machine-readable error code.
            `invalid_json` — request body is not valid JSON;
            `validation_error` — missing required fields or invalid data format;
            `event_not_supported` — unknown event type (method);
            `internal_error` — internal server error;
            `account_not_found` — account does not exist;
            `integration_not_found` — telephony integration not found;
            `telephony_deactivated` — telephony integration is deactivated;
            `account_expired` — account expired, integration automatically deactivated.
        message:
          type: string
          description: Human-readable error description
          example: "Missing required payload field: call_id"
