diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..3d52bcc --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,16 @@ +{ + "MD013": false, + "MD025": false, + "MD033": false, + "MD041": false, + "MD046": { + "style": "fenced" + }, + "MD009": { + "br_spaces": 2 + }, + "MD012": false, + "MD022": true, + "MD023": true, + "MD024": false +} diff --git a/CHANGELOG.md b/CHANGELOG.md index bc86b57..e339ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Table of Contents +- [r1.2](#r12) - [r1.1](#r11) **Please be aware that the project will have frequent updates to the main branch. There are no compatibility guarantees associated with code in any branch, including main, until it has been released. For example, changes may be reverted before a release is published. For the best results, use the latest published release.** @@ -13,6 +14,74 @@ The below sections record the changes for each API version in each release as fo - for subsequent release-candidate(s), only the delta to the previous release-candidate - for a public release, the consolidated changes since the previous public release +# r1.2 + +## Release Note + +This release contains the definition and documentation of + +- click-to-dial v0.1.0-alpha.2 + +The API definition(s) are based on + +- Commonalities v0.6.0 +- Identity and Consent Management v0.4.0 + +## click-to-dial v0.1.0-alpha.2 + +click-to-dial v0.1.0-alpha.2 is a pre-release version of the click-to-dial API. + +- API definition **with inline documentation**: + + - [View it on ReDoc](https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/camaraproject/ClickToDial/r1.2/code/API_definitions/click-to-dial.yaml&nocors) + - [View it on Swagger Editor](https://camaraproject.github.io/swagger-ui/?url=https://raw.githubusercontent.com/camaraproject/ClickToDial/r1.2/code/API_definitions/click-to-dial.yaml) + - OpenAPI [YAML spec file](https://github.com/camaraproject/ClickToDial/blob/r1.2/code/API_definitions/click-to-dial.yaml) + +### Added + +- Introduced the fully RESTful `/calls` resource model: + - `POST /calls` + - `GET /calls/{callId}` + - `DELETE /calls/{callId}` + - `GET /calls/{callId}/recording` +- CloudEvents-based call status change notifications: + - type: `org.camaraproject.click-to-dial.v0.status-changed` + - specversion: "1.0" +- Structured `422 UnprocessableEntity` business validation error codes: + - `INVALID_PHONE_NUMBER` + - `SAME_CALLER_CALLEE` + - `RECORDING_NOT_SUPPORTED` + - `CALLER_NOT_AVAILABLE` + - `CALLEE_NOT_AVAILABLE` + - `INSUFFICIENT_BALANCE` + - `RESTRICTED_DESTINATION` + +### Changed + +- Enum values aligned with CAMARA lowerCamelCase conventions for status and reason. +- Updated sink credential schema to enforce `credentialType = "ACCESSTOKEN"`. +- Updated BDD test definitions to reflect the `/calls` endpoints and versioned base paths. +- Improved alignment of OpenAPI definitions and markdown API documentation examples. + +### Fixed + +- Corrected CloudEvent examples to match defined schemas and enum values. +- Resolved inconsistencies between error examples and the `ErrorInfo` schema. + +### Removed + +- Removed deprecated operations from earlier alpha versions: + - `beginCall` + - `releaseCall` + - `downloadRecording` + These have been replaced by the RESTful `/calls` design. + +### New Contributors + +- N/A + +**Full Changelog**: [https://github.com/camaraproject/ClickToDial/commits/r1.2/](https://github.com/camaraproject/ClickToDial/commits/r1.2/) + # r1.1 ## Release Note @@ -29,6 +98,7 @@ The API definition(s) are based on ## click-to-dial v0.1.0-alpha.1 click-to-dial v0.1.0-alpha.1 is the first pre-release version of the click-to-dial API. + - API definition **with inline documentation**: - [View it on ReDoc](https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/camaraproject/ClickToDial/r1.1/code/API_definitions/click-to-dial.yaml&nocors) - [View it on Swagger Editor](https://camaraproject.github.io/swagger-ui/?url=https://raw.githubusercontent.com/camaraproject/ClickToDial/r1.1/code/API_definitions/click-to-dial.yaml) diff --git a/code/API_definitions/click-to-dial.yaml b/code/API_definitions/click-to-dial.yaml index bf41da0..ee96bd4 100644 --- a/code/API_definitions/click-to-dial.yaml +++ b/code/API_definitions/click-to-dial.yaml @@ -39,7 +39,7 @@ info: license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html - version: wip + version: 0.1.0-alpha.2 x-camara-commonalities: 0.6 externalDocs: @@ -47,7 +47,7 @@ externalDocs: url: https://github.com/camaraproject/ClickToDial servers: - - url: '{apiRoot}/click-to-dial/vwip' + - url: '{apiRoot}/click-to-dial/v0.1alpha2' variables: apiRoot: default: http://localhost:9091 @@ -94,7 +94,26 @@ paths: $ref: '#/components/responses/Conflict409' '422': $ref: '#/components/responses/UnprocessableEntity422' - + callbacks: + callStatusChanged: + '{$request.body#/sink}': + post: + summary: Call status changed event + description: | + Event notification delivered to the sink URL when the call status + changes (CloudEvents structured mode). + requestBody: + required: true + content: + application/cloudevents+json: + schema: + $ref: '#/components/schemas/EventCTDStatusChanged' + examples: + CALL_STATUS_CHANGED_EXAMPLE: + $ref: '#/components/examples/CALL_STATUS_CHANGED_EXAMPLE' + responses: + '2XX': + description: Event successfully received by the sink. /calls/{callId}: get: tags: diff --git a/code/Test_definitions/click-to-dial-createCall.feature b/code/Test_definitions/click-to-dial-createCall.feature index 1cef225..96e176d 100644 --- a/code/Test_definitions/click-to-dial-createCall.feature +++ b/code/Test_definitions/click-to-dial-createCall.feature @@ -1,4 +1,4 @@ -Feature: CAMARA Click to Dial API, vwip - Operation createCall +Feature: CAMARA Click to Dial API, v0.1.0-alpha.2 - Operation createCall # Input to be provided by the implementation to the tester # # Implementation indications: diff --git a/code/Test_definitions/click-to-dial-getRecording.feature b/code/Test_definitions/click-to-dial-getRecording.feature index 79b2376..9939f8b 100644 --- a/code/Test_definitions/click-to-dial-getRecording.feature +++ b/code/Test_definitions/click-to-dial-getRecording.feature @@ -1,4 +1,4 @@ -Feature: CAMARA Click to Dial API, vwip - Operation getRecording +Feature: CAMARA Click to Dial API, v0.1.0-alpha.2 - Operation getRecording # Input to be provided by the implementation to the tester # # Implementation indications: diff --git a/code/Test_definitions/click-to-dial-terminateCall.feature b/code/Test_definitions/click-to-dial-terminateCall.feature index 9b9e19e..c71f1e0 100644 --- a/code/Test_definitions/click-to-dial-terminateCall.feature +++ b/code/Test_definitions/click-to-dial-terminateCall.feature @@ -1,4 +1,4 @@ -Feature: CAMARA Click to Dial API, vwip - Operation terminateCall +Feature: CAMARA Click to Dial API, v0.1.0-alpha.2 - Operation terminateCall # Input to be provided by the implementation to the tester # # Implementation indications: diff --git a/documentation/API_documentation/click-to-dial-API-Readiness-Checklist.md b/documentation/API_documentation/click-to-dial-API-Readiness-Checklist.md index 21382b6..18790d8 100644 --- a/documentation/API_documentation/click-to-dial-API-Readiness-Checklist.md +++ b/documentation/API_documentation/click-to-dial-API-Readiness-Checklist.md @@ -1,22 +1,22 @@ # API Readiness Checklist -Checklist for click-to-dial v0.1.0.alpha in r1.1 +Checklist for click-to-dial v0.1.0-alpha.2 (Commonalities r3.3) -| Nr | API release assets | alpha | release-candidate | initial public | stable public | Status | Reference information | -| -- | -------------------------------------------- | :---: | :---------------: | :------------: | :-----------: | :----: | ------------------------------------------------------------------------------------------------------------------------- | -| 1 | API definition | M | M | M | M | Y | [/code/API_definitions/click-to-dial.yaml](/code/API_definitions/click-to-dial.yaml) | -| 2 | Design guidelines from Commonalities applied | O | M | M | M | Y | [r2.3](https://github.com/camaraproject/Commonalities/releases/tag/r2.3) | -| 3 | Guidelines from ICM applied | O | M | M | M | Y | [r2.3](https://github.com/camaraproject/IdentityAndConsentManagement/releases/tag/r2.3) | -| 4 | API versioning convention applied | M | M | M | M | Y | | -| 5 | API documentation | M | M | M | M | Y | [/code/API_definitions/click-to-dial_API.md](/code/API_definitions/click-to-dial_API.md) | -| 6 | User stories | O | O | O | M | Y | [/documentation/API_documentation/click-to-dial_User_Story.md](/documentation/API_documentation/click-to-dial_User_Story.md) | -| 7 | Basic API test cases & documentation | O | M | M | M | Y | [/code/Test_definitions](/code/Test_definitions) | -| 8 | Enhanced API test cases & documentation | O | O | O | M | Y | [/code/Test_definitions](/code/Test_definitions) | -| 9 | Test result statement | O | O | O | M | N | | -| 10 | API release numbering convention applied | M | M | M | M | Y | | -| 11 | Change log updated | M | M | M | M | Y | [/CHANGELOG.md](/CHANGELOG.md) | -| 12 | Previous public release was certified | O | O | O | M | Y | | -| 13 | API description (for marketing) | O | O | M | M | | [wiki link](https://lf-camaraproject.atlassian.net/wiki/xxx) | +| Nr | API release assets | alpha | release-candidate | initial public | stable public | Status | Reference information | +| -- | -------------------------------------------- | :---: | :---------------: | :------------: | :-----------: | :----: | ------------------------------------------------------------------------------------------ | +| 1 | API definition | M | M | M | M | Y | [code/API_definitions/click-to-dial.yaml](../../code/API_definitions/click-to-dial.yaml) | +| 2 | Design guidelines from Commonalities applied | O | M | M | M | Y | [r3.3](https://github.com/camaraproject/Commonalities/releases/tag/r3.3) | +| 3 | Guidelines from ICM applied | O | M | M | M | Y | [r3.3](https://github.com/camaraproject/IdentityAndConsentManagement/releases/tag/r3.3) | +| 4 | API versioning convention applied | M | M | M | M | Y | | +| 5 | API documentation | M | M | M | M | Y | [documentation/API_documentation/click-to-dial_API.md](../../documentation/API_documentation/click-to-dial_API.md) | +| 6 | User stories | O | O | O | M | Y | [documentation/API_documentation/click-to-dial_User_Story.md](../../documentation/API_documentation/click-to-dial_User_Story.md) | +| 7 | Basic API test cases & documentation | O | M | M | M | Y | [code/Test_definitions](../../code/Test_definitions) | +| 8 | Enhanced API test cases & documentation | O | O | O | M | Y | [code/Test_definitions](../../code/Test_definitions) | +| 9 | Test result statement | O | O | O | M | N | | +| 10 | API release numbering convention applied | M | M | M | M | Y | | +| 11 | Change log updated | M | M | M | M | Y | [CHANGELOG.md](../../CHANGELOG.md) | +| 12 | Previous public release was certified | O | O | O | M | N | | +| 13 | API description (for marketing) | O | O | M | M | Y | [wiki link](https://lf-camaraproject.atlassian.net/wiki/spaces/CAM/pages/14561756/) | To fill the checklist: @@ -27,4 +27,4 @@ To fill the checklist: Note: the checklists of a public API version and of its preceding release-candidate API version can be the same. -The documentation for the content of the checklist is here: see API Readiness Checklist section in the [API Release Process](https://lf-camaraproject.atlassian) +The documentation for the content of the checklist is here: see API Readiness Checklist section in the [API Release Process](https://lf-camaraproject.atlassian.net) diff --git a/documentation/API_documentation/click-to-dial_API.md b/documentation/API_documentation/click-to-dial_API.md index 59d33d7..220d8ef 100644 --- a/documentation/API_documentation/click-to-dial_API.md +++ b/documentation/API_documentation/click-to-dial_API.md @@ -98,7 +98,7 @@ This API uses **OpenID Connect** for authentication and authorization. Obtain yo ### 4.1 API Version -wip +0.1.0-alpha.2 ### 4.2 Details @@ -113,20 +113,20 @@ wip ##### Click to Dial Initiation Request -| Name | Description | Required | Example | -| -------------- | --------------------------------------------------- | -------- | ---------------------------- | -| caller | Calling party number (E.164, with "+") | Yes | "+12345678" | -| callee | Called party number (E.164, with "+") | Yes | "+87654321" | +| Name | Description | Required | Example | +| -------------- | --------------------------------------------------- | -------- | -------------------------------- | +| caller | Calling party number (E.164, with "+") | Yes | "+12345678" | +| callee | Called party number (E.164, with "+") | Yes | "+87654321" | | sink | (Optional) Callback URL for status notifications | No | `` | -| sinkCredential | (Optional) Callback authentication info (see below) | No | (see below) | +| sinkCredential | (Optional) Callback authentication info (see below) | No | (see below) | ##### sinkCredential (for `ACCESSTOKEN` type) The `SinkCredential` is a discriminator-based object. Currently the only supported `credentialType` is `ACCESSTOKEN` and the concrete `AccessTokenCredential` MUST include the fields below. -| Name | Description | Required | Example | -| --------------------- | --------------------------------------------- | -------- | ---------------------- | -| credentialType | Must be `ACCESSTOKEN` (discriminator) | Yes | `ACCESSTOKEN` | +| Name | Description | Required | Example | +| --------------------- | --------------------------------------------- | -------- | ------------------------ | +| credentialType | Must be `ACCESSTOKEN` (discriminator) | Yes | `ACCESSTOKEN` | | accessToken | Access token used to authenticate event POSTs | Yes | `sink_token` | | accessTokenExpiresUtc | UTC expiry timestamp for the access token | Yes | `2025-12-31T23:59:59Z` | | accessTokenType | Token type (OAuth token type) | Yes | `bearer` | @@ -135,16 +135,16 @@ The `SinkCredential` is a discriminator-based object. Currently the only support Status notifications are delivered as CloudEvents (see CloudEvent section below). The `data` payload for `EventCTDStatusChanged` contains the following fields. Note that `status` is now an object containing `state` and optional `reason`. -| Name | Description | Required | Notes/Values | -| --------------- | -------------------------------------------------------- | -------- | ----------------------------------------------------------------------------- | -| caller | Calling party number (E.164) | Yes | | -| callee | Called party number (E.164) | Yes | | -| status | Object with `state` (lifecycle) and optional `reason` | Yes | `state`: `initiating`,`callingCaller`,`callingCallee`,`connected`,`disconnected`,`failed` | -| status.reason | Disconnection reason (if `state` is `disconnected`) | Cond. | `hangUp`,`callerBusy`,`callerNoAnswer`,`callerFailure`,`callerAbandon`,`calleeBusy`,`calleeNoAnswer`,`calleeFailure`,`other` | -| recordingResult | Recording result when recording enabled | Cond. | `success`,`noRecord`,`fail` | -| callDuration | Duration in seconds (present when call ended) | Cond. | | -| timestamp | UTC timestamp of the event / state change | Yes | RFC 3339 format | -| callId | Identifier of the call (matches the `callId` returned) | Yes | UUID style string | +| Name | Description | Required | Notes/Values | +| --------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| caller | Calling party number (E.164) | Yes | | +| callee | Called party number (E.164) | Yes | | +| status | Object with `state` (lifecycle) and optional `reason` | Yes | `state`: `initiating`,`callingCaller`,`callingCallee`,`connected`,`disconnected`,`failed` | +| status.reason | Disconnection reason (if `state` is `disconnected`) | Cond. | `hangUp`,`callerBusy`,`callerNoAnswer`,`callerFailure`,`callerAbandon`,`calleeBusy`,`calleeNoAnswer`,`calleeFailure`,`other` | +| recordingResult | Recording result when recording enabled | Cond. | `success`,`noRecord`,`fail` | +| callDuration | Duration in seconds (present when call ended) | Cond. | | +| timestamp | UTC timestamp of the event / state change | Yes | RFC 3339 format | +| callId | Identifier of the call (matches the `callId` returned) | Yes | UUID style string | #### Release Call @@ -156,12 +156,12 @@ Status notifications are delivered as CloudEvents (see CloudEvent section below) ### 4.3 Endpoint Definitions -| Endpoint | Method | Description | -| ------------------------------------ | ------ | ---------------------------------- | -| /calls | POST | Start a click-to-dial call | -| /calls/{callId} | GET | Retrieve call details | -| /calls/{callId} | DELETE | End (release) a click-to-dial call | -| /calls/{callId}/recording | GET | Download call recording | +| Endpoint | Method | Description | +| ------------------------- | ------ | ---------------------------------- | +| /calls | POST | Start a click-to-dial call | +| /calls/{callId} | GET | Retrieve call details | +| /calls/{callId} | DELETE | End (release) a click-to-dial call | +| /calls/{callId}/recording | GET | Download call recording | ### 4.4 Errors @@ -177,12 +177,91 @@ Errors follow the standard format: Reference the [OpenAPI YAML](../../code/API_definitions/click-to-dial.yaml) for exact error codes and descriptions. +#### 4.4.1 422 — Business error codes (Unprocessable Entity) + +When a request is syntactically correct but semantically invalid, the API returns `422 Unprocessable Entity` with a business `code` describing the error. The following 422 business error codes are defined in the OpenAPI spec: + +| Code | Description | +| ---- | ----------- | +| `INVALID_PHONE_NUMBER` | Caller or callee number is not a valid E.164 phone number. | +| `SAME_CALLER_CALLEE` | Caller and callee cannot be the same number. | +| `RECORDING_NOT_SUPPORTED` | Recording is not supported for this call. | +| `CALLER_NOT_AVAILABLE` | Caller number is currently not reachable or not allowed to start a call. | +| `CALLEE_NOT_AVAILABLE` | Callee number is currently not reachable or not allowed to receive a call. | +| `INSUFFICIENT_BALANCE` | The account does not have sufficient balance or quota to start this call. | +| `RESTRICTED_DESTINATION` | The destination number is restricted and cannot be called. | + +Example 422 response: + +```json +{ + "status": 422, + "code": "INVALID_PHONE_NUMBER", + "message": "Caller or callee number is not a valid E.164 phone number." +} +``` + +Note: Additional common CAMARA error responses may be defined in the `CAMARA_common.yaml` referenced by this API's readiness checklist. Always consult the OpenAPI YAML for the authoritative list. + ## CloudEvent delivery notes - Providers MUST send status notifications as CloudEvents in structured mode. The HTTP header must be `Content-Type: application/cloudevents+json`. - The CloudEvent MUST include the attributes: `id`, `source`, `type`, `specversion`, `time`, and `subject` (subject should identify the resource, e.g. `/calls/{callId}`). - The CloudEvent attribute `datacontenttype` MUST be `application/json` for the `data` payload. +The CloudEvent `data` payload for Click-to-Dial `EventCTDStatusChanged` includes `callId`, `caller`, `callee`, `timestamp` and `status` (where `status` is an object with `state` and optional `reason`). Providers MUST set the CloudEvent `subject` to the affected resource (for example `/calls/{callId}`) and set `datacontenttype` to `application/json`. + +Example CloudEvent (structured mode) — `CALL_STATUS_CHANGED_EXAMPLE` from the OpenAPI spec: + +```json +{ + "id": "83a0d986-0866-4f38-b8c0-fc65bfcda452", + "source": "https://api.example.com/click-to-dial", + "subject": "/calls/123e4567-e89b-12d3-a456-426614174000", + "specversion": "1.0", + "datacontenttype": "application/json", + "type": "org.camaraproject.click-to-dial.v0.status-changed", + "time": "2021-12-12T00:00:00Z", + "data": { + "callId": "123e4567-e89b-12d3-a456-426614174000", + "caller": "+12345678", + "callee": "+12345678", + "status": { + "state": "disconnected", + "reason": "hangUp" + }, + "recordingResult": "success", + "callDuration": "1600", + "timestamp": "2017-12-04T18:07:57Z" + } +} +``` + +Providers SHOULD ensure event `id` uniqueness and may retry delivery on transient failures. When delivering events to a `sink` that requires authentication, use the provided `sinkCredential` (e.g., set `Authorization: Bearer ` for `ACCESSTOKEN`). + +## Call flow (sequence diagram) + +The following sequence diagram shows the typical flow when creating a Click-to-Dial session, delivering events, and terminating the call. This is a logical flow; implementers may optimize or parallelise steps. + +```mermaid +sequenceDiagram + participant Client + participant Provider + participant Caller + participant Callee + participant Sink + + Client->>Provider: POST /calls {caller, callee, sink, sinkCredential} + Provider->>Caller: Place outbound call to caller + Provider->>Callee: Place outbound call to callee + Provider-->>Client: 201 Created {callId, status} + Provider->>Sink: CloudEvent (status: initiating) + Provider->>Sink: CloudEvent (status: connected) + Client->>Provider: DELETE /calls/{callId} + Provider->>Sink: CloudEvent (status: disconnected) +``` + + ### 4.5 Policies N/A @@ -233,7 +312,7 @@ N/A ### 4.9 Release Notes This section lists release notes for historical versions. -The current version on the main branch is **wip**. +The current version on the main branch is **0.1.0-alpha.2**. - **0.1.0-alpha.1** — Initial release (archived).