# yaml-language-server: $schema=https://json-schema.org/draft/2020-12/schema openapi: 3.1.0 info: title: agent-call Asterisk/SIP Management API version: 1.0.0 description: >- Independent Asterisk/SIP management backend. Admin write operations are separate from the SaaS read-only Trunk directory and from ordinary scheduling APIs. In mock mode publication records are intents only. In real mode a publication is successful only after every selected Cell Agent returns a matching mTLS acknowledgement. servers: - url: https://sip-admin.internal description: Restricted operator management network - url: https://sip-read.internal description: SaaS read-only service network tags: - name: health - name: admin-trunks - name: admin-cells - name: saas-readonly paths: /healthz/live: get: tags: [health] operationId: live responses: '200': description: Service is alive content: application/json: schema: $ref: '#/components/schemas/Health' /admin/v1/trunks: get: tags: [admin-trunks] operationId: listAdminTrunks security: [{SipAdminBearer: []}] responses: '200': description: All Trunks, including unpublished revisions content: application/json: schema: $ref: '#/components/schemas/AdminTrunkList' '401': {$ref: '#/components/responses/Unauthorized'} '403': {$ref: '#/components/responses/Forbidden'} /admin/v1/trunks/{trunk_id}: parameters: - {$ref: '#/components/parameters/TrunkId'} get: tags: [admin-trunks] operationId: getAdminTrunk security: [{SipAdminBearer: []}] responses: '200': description: Trunk configuration and revisions content: application/json: schema: $ref: '#/components/schemas/AdminTrunk' '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} put: tags: [admin-trunks] operationId: createTrunkRevision security: [{SipAdminBearer: []}] parameters: - {$ref: '#/components/parameters/IfMatch'} - {$ref: '#/components/parameters/RequestId'} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TrunkConfig' responses: '200': description: New draft revision for an existing Trunk content: application/json: schema: {$ref: '#/components/schemas/AdminTrunk'} '201': description: New Trunk with its first draft revision content: application/json: schema: {$ref: '#/components/schemas/AdminTrunk'} '400': {$ref: '#/components/responses/BadRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /admin/v1/trunks/{trunk_id}/publish: parameters: - {$ref: '#/components/parameters/TrunkId'} post: tags: [admin-trunks] operationId: publishTrunk security: [{SipAdminBearer: []}] parameters: - {$ref: '#/components/parameters/IfMatch'} - {$ref: '#/components/parameters/RequestId'} responses: '200': description: >- Published revision after all selected Cell acknowledgements content: application/json: schema: {$ref: '#/components/schemas/AdminTrunk'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /admin/v1/trunks/{trunk_id}/disable: parameters: - {$ref: '#/components/parameters/TrunkId'} post: tags: [admin-trunks] operationId: disableTrunk security: [{SipAdminBearer: []}] parameters: - {$ref: '#/components/parameters/IfMatch'} - {$ref: '#/components/parameters/RequestId'} responses: '200': description: Trunk disabled after selected Cell acknowledgements content: application/json: schema: {$ref: '#/components/schemas/AdminTrunk'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /admin/v1/trunks/{trunk_id}/rollback: parameters: - {$ref: '#/components/parameters/TrunkId'} post: tags: [admin-trunks] operationId: rollbackTrunk security: [{SipAdminBearer: []}] parameters: - {$ref: '#/components/parameters/IfMatch'} - {$ref: '#/components/parameters/RequestId'} requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [target_revision] properties: target_revision: {type: integer, minimum: 1} responses: '200': description: >- New revision copied from the target after Cell acknowledgements content: application/json: schema: {$ref: '#/components/schemas/AdminTrunk'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /admin/v1/trunks/{trunk_id}/publications: parameters: - {$ref: '#/components/parameters/TrunkId'} get: tags: [admin-trunks] operationId: listTrunkPublications security: [{SipAdminBearer: []}] responses: '200': description: Per-Cell publication intents content: application/json: schema: type: object required: [mode, publications] properties: mode: {$ref: '#/components/schemas/Mode'} publications: type: array items: {$ref: '#/components/schemas/Publication'} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /admin/v1/trunks/{trunk_id}/audit: parameters: - {$ref: '#/components/parameters/TrunkId'} get: tags: [admin-trunks] operationId: listTrunkAudit security: [{SipAdminBearer: []}] responses: '200': description: Immutable management audit entries content: application/json: schema: type: object required: [mode, audit] properties: mode: {$ref: '#/components/schemas/Mode'} audit: type: array items: {$ref: '#/components/schemas/AuditEntry'} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /admin/v1/cells: get: tags: [admin-cells] operationId: listCells security: [{SipAdminBearer: []}] responses: '200': description: Registered multi-machine voice Cells content: application/json: schema: type: object required: [mode, cells] properties: mode: {$ref: '#/components/schemas/Mode'} cells: type: array items: {$ref: '#/components/schemas/Cell'} '401': {$ref: '#/components/responses/Unauthorized'} /admin/v1/cells/{cell_id}: parameters: - {$ref: '#/components/parameters/CellId'} put: tags: [admin-cells] operationId: registerCell security: [{SipAdminBearer: []}] parameters: - {$ref: '#/components/parameters/IfMatch'} - {$ref: '#/components/parameters/RequestId'} requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/CellConfig'} responses: '200': description: Updated Cell revision content: application/json: schema: {$ref: '#/components/schemas/Cell'} '201': description: Registered Cell content: application/json: schema: {$ref: '#/components/schemas/Cell'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /readonly/v1/sip/trunks: get: tags: [saas-readonly] operationId: listAuthorizedTrunks security: [{SaasTrunkReadBearer: []}] responses: '200': description: Published Trunks authorized for this SaaS principal content: application/json: schema: $ref: '#/components/schemas/ReadonlyTrunkList' '401': {$ref: '#/components/responses/Unauthorized'} /readonly/v1/sip/trunks/{trunk_id}: parameters: - {$ref: '#/components/parameters/TrunkId'} get: tags: [saas-readonly] operationId: getAuthorizedTrunk security: [{SaasTrunkReadBearer: []}] responses: '200': description: Published, sanitized Trunk metadata content: application/json: schema: {$ref: '#/components/schemas/ReadonlyTrunk'} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} components: securitySchemes: SipAdminBearer: type: http scheme: bearer bearerFormat: opaque description: >- Dedicated operator/backend credential for SIP management writes. It is not accepted by the SaaS read-only API or ordinary scheduling API. SaasTrunkReadBearer: type: http scheme: bearer bearerFormat: opaque description: >- Dedicated SaaS read-only credential. It cannot publish, modify, disable, rollback, or access Cell management. parameters: TrunkId: name: trunk_id in: path required: true schema: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'} CellId: name: cell_id in: path required: true schema: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'} IfMatch: name: If-Match in: header required: true description: Exact latest revision required for CAS; quotes are accepted. schema: {type: integer, minimum: 0} RequestId: name: X-Request-ID in: header required: true schema: {type: string, minLength: 1, maxLength: 128} responses: BadRequest: description: Invalid configuration or request content: application/json: schema: {$ref: '#/components/schemas/ErrorResponse'} Unauthorized: description: Missing or wrong authentication domain content: application/json: schema: {$ref: '#/components/schemas/ErrorResponse'} Forbidden: description: Credential lacks the required scope content: application/json: schema: {$ref: '#/components/schemas/ErrorResponse'} Conflict: description: CAS conflict or no compatible Cell content: application/json: schema: {$ref: '#/components/schemas/ErrorResponse'} NotFound: description: Resource is not visible or does not exist content: application/json: schema: {$ref: '#/components/schemas/ErrorResponse'} schemas: Mode: type: string enum: [mock, real] Health: type: object additionalProperties: false required: [status, mode] properties: status: {type: string, const: ok} mode: {$ref: '#/components/schemas/Mode'} CodecProfile: type: object additionalProperties: false required: [allowed, preferred] properties: allowed: type: array minItems: 1 uniqueItems: true items: {type: string, enum: [PCMA, PCMU]} preferred: {type: string, enum: [PCMA, PCMU]} SipConfig: type: object additionalProperties: false required: [host, port, transport, auth_mode, register] properties: host: {type: string, minLength: 1, maxLength: 253} port: {type: integer, minimum: 1, maximum: 65535} transport: {type: string, enum: [udp, tcp, tls]} auth_mode: {type: string, enum: [ip, digest]} register: {type: boolean} credential_ref: type: string writeOnly: true description: >- Secret-store reference only; plaintext credentials are forbidden. TrunkConfig: type: object additionalProperties: false required: - display_name - enabled - sip - codec_profile - caller_ids - dial_prefix - egress_pool_id - max_concurrency - max_cps properties: display_name: {type: string, minLength: 1, maxLength: 256} enabled: {type: boolean} sip: {$ref: '#/components/schemas/SipConfig'} codec_profile: {$ref: '#/components/schemas/CodecProfile'} caller_ids: type: array minItems: 1 uniqueItems: true items: {type: string, minLength: 1, maxLength: 128} dial_prefix: {type: string, maxLength: 32} egress_pool_id: type: string pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' max_concurrency: {type: integer, minimum: 1} max_cps: {type: integer, minimum: 1} CellConfig: type: object additionalProperties: false required: [egress_pool_id, codec_capabilities, status, max_concurrency] properties: egress_pool_id: type: string pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' codec_capabilities: type: array minItems: 1 uniqueItems: true items: {type: string, enum: [PCMA, PCMU]} status: {type: string, enum: [healthy, draining, disabled]} max_concurrency: {type: integer, minimum: 1} management_url: type: string format: uri pattern: '^https://' description: >- mTLS Cell Agent endpoint. Required when mode=real; credentials and query strings are not allowed. RevisionInfo: type: object additionalProperties: false required: [revision, state, created_at, created_by] properties: revision: {type: integer, minimum: 1} state: {type: string, enum: [draft, publishing, published, superseded]} created_at: {type: string, format: date-time} created_by: {type: string} AdminTrunk: type: object required: - mode - trunk_id - latest_revision - active_revision - status - compatible_cell_ids - latest - active - versions properties: mode: {$ref: '#/components/schemas/Mode'} trunk_id: {type: string} latest_revision: {type: integer, minimum: 1} active_revision: {type: integer, minimum: 0} status: {type: string, enum: [draft, published, disabled]} updated_at: {type: string, format: date-time} compatible_cell_ids: {type: array, items: {type: string}} latest: {$ref: '#/components/schemas/TrunkView'} active: {$ref: '#/components/schemas/TrunkView'} versions: type: array items: $ref: '#/components/schemas/RevisionInfo' TrunkView: allOf: - {$ref: '#/components/schemas/TrunkConfig'} - type: object properties: trunk_id: {type: string} credential_configured: {type: boolean} asterisk_allow: type: array items: {type: string, enum: [alaw, ulaw]} ReadonlyTrunk: type: object required: - mode - trunk_id - revision - status - config properties: mode: {$ref: '#/components/schemas/Mode'} trunk_id: {type: string} revision: {type: integer, minimum: 1} status: {type: string, const: published} updated_at: {type: string, format: date-time} config: {$ref: '#/components/schemas/TrunkView'} AdminTrunkList: type: object required: [mode, trunks] properties: mode: {$ref: '#/components/schemas/Mode'} trunks: {type: array, items: {$ref: '#/components/schemas/AdminTrunk'}} ReadonlyTrunkList: type: object required: [mode, trunks] properties: mode: {$ref: '#/components/schemas/Mode'} trunks: type: array items: $ref: '#/components/schemas/ReadonlyTrunk' Cell: type: object required: [mode, cell_id, revision, config, updated_at, updated_by] properties: mode: {$ref: '#/components/schemas/Mode'} cell_id: {type: string} revision: {type: integer, minimum: 1} config: {$ref: '#/components/schemas/CellConfig'} updated_at: {type: string, format: date-time} updated_by: {type: string} Publication: type: object required: [trunk_id, revision, cell_id, status, updated_at] properties: trunk_id: {type: string} revision: {type: integer, minimum: 1} cell_id: {type: string} status: {type: string, enum: [pending, applied, failed]} error_code: {type: [string, 'null']} updated_at: {type: string, format: date-time} AuditEntry: type: object required: - audit_id - resource_type - resource_id - action - revision - actor - details_json - created_at properties: audit_id: {type: string} resource_type: {type: string, const: trunk} resource_id: {type: string} action: type: string enum: [ upsert, publish, publish_failed, disable, disable_failed, rollback, rollback_failed, ] revision: {type: integer, minimum: 0} actor: {type: string} request_id: {type: [string, 'null']} details_json: {type: string} created_at: {type: string, format: date-time} ErrorResponse: type: object required: [error] properties: error: type: object required: [code, message] properties: code: {type: string} message: {type: string}