diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index 9e6c92dd..251e9c0e 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -166,6 +166,7 @@ bandwidth/models/rbm_message_content_rich_card.py bandwidth/models/rbm_message_content_text.py bandwidth/models/rbm_message_media.py bandwidth/models/rbm_open_url_enum.py +bandwidth/models/rbm_session.py bandwidth/models/rbm_standalone_card.py bandwidth/models/rbm_suggestion_response.py bandwidth/models/rbm_web_view_enum.py @@ -377,6 +378,7 @@ docs/RbmMessageContentRichCard.md docs/RbmMessageContentText.md docs/RbmMessageMedia.md docs/RbmOpenUrlEnum.md +docs/RbmSession.md docs/RbmStandaloneCard.md docs/RbmSuggestionResponse.md docs/RbmWebViewEnum.md diff --git a/README.md b/README.md index 7c27d4ea..ce3d6192 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,7 @@ Class | Method | HTTP request | Description - [RbmMessageContentText](docs/RbmMessageContentText.md) - [RbmMessageMedia](docs/RbmMessageMedia.md) - [RbmOpenUrlEnum](docs/RbmOpenUrlEnum.md) + - [RbmSession](docs/RbmSession.md) - [RbmStandaloneCard](docs/RbmStandaloneCard.md) - [RbmSuggestionResponse](docs/RbmSuggestionResponse.md) - [RbmWebViewEnum](docs/RbmWebViewEnum.md) diff --git a/bandwidth.yml b/bandwidth.yml index 350b6217..0df9d764 100644 --- a/bandwidth.yml +++ b/bandwidth.yml @@ -2031,6 +2031,7 @@ components: messageLength: type: integer description: The length of the message content. + nullable: true example: 18 attachmentCount: type: integer @@ -2620,14 +2621,13 @@ components: type: string description: Displayed text for user to click maxLength: 25 - example: Hello world + example: Track my order rbmActionPostbackData: title: Post Back Data type: string - format: byte - description: Base64 payload the customer receives when the reply is clicked. + description: Payload the customer receives when the reply is clicked. maxLength: 2048 - example: SGVsbG8gd29ybGQ= + example: track-order-43654 rbmActionBase: type: object properties: @@ -2916,8 +2916,8 @@ components: properties: text: type: string - description: The text associated with the suggestion response. - example: Yes, I would like to proceed + description: The text of the suggestion the user tapped. + example: Track my order postbackData: $ref: '#/components/schemas/rbmActionPostbackData' pairedMessageId: @@ -2938,6 +2938,70 @@ components: format: double description: The longitude of the client's location. example: '-122.4194' + rbmSession: + type: object + description: >- + RCS interactive session information for the message. + + An interactive session is a 24-hour window covering all Rich and Rich + Media messages exchanged between an + + RCS agent and a specific end user mobile number. It is triggered when at + least 4 messages are exchanged within + + 24 hours, at least 2 of them mobile-originated and at least 1 + mobile-terminated. + + Only present on `message-delivered`, `message-received` and + `session-started` callbacks for RBM messages of + + US agents with the Conversational billing category. Omitted for all + other messages. + properties: + canInitiate: + type: boolean + description: >- + Whether the message can start an interactive session. + + `false` if the message is already part of an active session, or if + the agent is not eligible for + + interactive session billing. + example: false + id: + type: string + nullable: true + description: >- + The unique identifier of the interactive session the message is part + of. This is not a message ID. + + Omitted if the message is not part of a session. + example: 0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d + type: + type: string + nullable: true + description: |- + The type of the interactive session. + Only present on `session-started` callbacks. + example: INTERACTIVE_SESSION + startTime: + type: string + nullable: true + format: date-time + description: >- + The time the 24-hour session window opened. Only present on + `session-started` callbacks. + example: '2026-08-06T09:59:55Z' + endTime: + type: string + nullable: true + format: date-time + description: >- + The time the 24-hour session window closes. Only present on + `session-started` callbacks. + example: '2026-08-07T09:59:55Z' + required: + - canInitiate callback: description: >- Callbacks are divided into two types based on direction of the related @@ -2960,6 +3024,7 @@ components: message-delivered: '#/components/schemas/statusCallback' message-failed: '#/components/schemas/statusCallback' message-read: '#/components/schemas/statusCallback' + session-started: '#/components/schemas/statusCallback' message-received: '#/components/schemas/inboundCallback' requested-location-response: '#/components/schemas/inboundCallback' suggestion-response: '#/components/schemas/inboundCallback' @@ -3060,12 +3125,17 @@ components: - `message-read` indicates that the RBM message was read by the recipient. + + - `session-started` indicates that a previously exchanged RBM message is + now part of an RCS interactive session and is no longer billed + individually. enum: - message-sending - message-sent - message-delivered - message-failed - message-read + - session-started example: message-delivered inboundCallbackTypeEnum: type: string @@ -3143,6 +3213,8 @@ components: $ref: '#/components/schemas/priorityEnum' channel: $ref: '#/components/schemas/multiChannelMessageChannelEnum' + session: + $ref: '#/components/schemas/rbmSession' required: - id - owner @@ -4876,6 +4948,10 @@ components: $ref: '#/components/schemas/sipCallId' sipResponseCode: $ref: '#/components/schemas/sipResponseCode' + sipStatusLine: + $ref: '#/components/schemas/sipStatusLine' + sipHeaders: + $ref: '#/components/schemas/sipHeaders' dtmfCallback: type: object description: >- @@ -5395,6 +5471,12 @@ components: $ref: '#/components/schemas/errorMessage' errorId: $ref: '#/components/schemas/errorId' + sipResponseCode: + $ref: '#/components/schemas/sipResponseCode' + sipStatusLine: + $ref: '#/components/schemas/sipStatusLine' + sipHeaders: + $ref: '#/components/schemas/sipHeaders' eventType: type: string description: >- @@ -5710,23 +5792,44 @@ components: additionalProperties: type: string description: >- - (optional) Map of customer-supplied X-* headers from the original - INVITE. Keys are lowercase (SIP headers are case-insensitive). Present - only for inbound SIP URI calls with custom headers. Note - keys preserve - the original SIP header name in lowercase rather than Bandwidth's usual - camelCase JSON convention, since these are passthrough values from the - caller's SIP INVITE, not Bandwidth-defined fields. If the same header - name is sent more than once in the INVITE, only the last value is kept. + (optional) Map of SIP headers, with lowercase keys (SIP headers are + case-insensitive). On the Initiate callback, contains the + customer-supplied X-* headers from the original INVITE; present only for + inbound SIP URI calls with custom headers. On the Disconnect and + Transfer Disconnect callbacks, contains a single `reason` key holding + the raw, unmodified `Reason` header value from the final SIP response + that rejected an outbound call (or transferred leg); present only when + that response included a `Reason` header (for example, `v=analytics1` + call-analytics block information from a terminating carrier). If the + response carries more than one `Reason` header, all values are returned + in the order received, separated by `, `. Note - on the Initiate + callback, keys preserve the original SIP header name in lowercase rather + than Bandwidth's usual camelCase JSON convention, since these are + passthrough values from the caller's SIP INVITE, not Bandwidth-defined + fields. If the same header name is sent more than once in the INVITE, + only the last value is kept. The example shown is for the Initiate + callback; on the Disconnect and Transfer Disconnect callbacks the only + key is `reason`. example: x-custom-header: customer-value x-session-id: sess-12345 sipResponseCode: type: integer description: >- - (optional) The SIP status code returned by Bandwidth's SBC when it - rejected an outbound call's INVITE (e.g. 486 for busy, 603 for decline). - Present only when an outbound call was rejected by the SBC. + (optional) The SIP status code of the final response that rejected an + outbound call's (or transferred leg's) INVITE (e.g. 486 for busy, 603 + for decline). Present only when the call was rejected with a SIP + response. example: 486 + sipStatusLine: + type: string + description: >- + (optional) The full SIP status line, exactly as received, of the final + response that rejected an outbound call's INVITE. The reason phrase can + help distinguish outcomes that share a status code, such as `603 Network + Blocked` and `603 Decline`. Present only when an outbound call was + rejected with a SIP response. + example: SIP/2.0 603 Network Blocked codeRequest: type: object properties: @@ -8878,6 +8981,31 @@ components: media: - >- https://messaging.bandwidth.com/api/v2/users/9900902/media/14762070468292kw2fuqty55yp2b2/0/bw.png + rbmMessageReceivedSessionCallbackExample: + summary: >- + An example of a rbm message-received callback body for a message that is + part of an RCS interactive session. + value: + time: '2026-08-06T10:00:05.502180Z' + type: message-received + to: BandwidthRBM + description: Incoming message received + message: + id: 1786010405502l2ntaoajuqjhqnxu + owner: BandwidthRBM + applicationId: 93de2206-9669-4e07-948d-329f4b722ee2 + time: '2026-08-06T10:00:05.302180Z' + segmentCount: 1 + direction: in + to: + - BandwidthRBM + from: '+12345678902' + channel: RBM + content: + text: Yes, please book it for tomorrow. + session: + canInitiate: false + id: 0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d messageSendingCallbackExample: summary: An example of a message-sending callback body. value: @@ -8977,6 +9105,79 @@ components: owner: '+12345678902' direction: out segmentCount: 1 + rbmMessageDeliveredSessionCallbackExample: + summary: >- + An example of a rbm message-delivered callback body for a message that + is part of an RCS interactive session. + value: + type: message-delivered + time: '2026-08-06T10:01:11.160744Z' + description: Message delivered to handset. + to: '+12345678902' + message: + id: 1786010470868ty55yp2b2kw2fuq + time: '2026-08-06T10:01:10.868292Z' + to: + - '+12345678902' + from: BandwidthRBM + text: '' + applicationId: 93de2206-9669-4e07-948d-329f4b722ee2 + owner: BandwidthRBM + direction: out + segmentCount: 1 + channel: RBM + session: + canInitiate: false + id: 0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d + rbmMessageDeliveredCanInitiateSessionCallbackExample: + summary: >- + An example of a rbm message-delivered callback body for a message that + is not yet part of an RCS interactive session. + value: + type: message-delivered + time: '2026-08-06T09:59:56.160744Z' + description: Message delivered to handset. + to: '+12345678902' + message: + id: 1786010395000kw2fuqty55yp2b2 + time: '2026-08-06T09:59:55.000000Z' + to: + - '+12345678902' + from: BandwidthRBM + text: '' + applicationId: 93de2206-9669-4e07-948d-329f4b722ee2 + owner: BandwidthRBM + direction: out + segmentCount: 1 + channel: RBM + session: + canInitiate: true + sessionStartedCallbackExample: + summary: An example of a session-started callback body. + value: + type: session-started + time: '2026-08-06T10:00:00.123000Z' + description: Message is now part of an RCS interactive session. + to: '+12345678902' + message: + id: 1786010395000kw2fuqty55yp2b2 + owner: BandwidthRBM + applicationId: 93de2206-9669-4e07-948d-329f4b722ee2 + time: '2026-08-06T10:00:00.120000Z' + segmentCount: 1 + direction: out + to: + - '+12345678902' + from: BandwidthRBM + text: '' + tag: '' + channel: RBM + session: + canInitiate: false + id: 0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d + type: INTERACTIVE_SESSION + startTime: '2026-08-06T09:59:55Z' + endTime: '2026-08-07T09:59:55Z' messageFailedCallbackExample: summary: An example of a message-failed callback body. value: @@ -9980,6 +10181,9 @@ components: $ref: '#/components/examples/smsMessageReceivedCallbackExample' mmsMessageReceivedCallback: $ref: '#/components/examples/mmsMessageReceivedCallbackExample' + rbmMessageReceivedSessionCallback: + $ref: >- + #/components/examples/rbmMessageReceivedSessionCallbackExample responses: '200': description: OK @@ -9995,8 +10199,9 @@ components: information regarding a message sent (MT) from your message-enabled Bandwidth telephone number. -
The payload type will be one of message-sending, message-sent, message-delivered, message-failed or message-read.
-
Note that message-read callbacks are pertinent only for RBM messages sent from the /messages/multiChannel endpoint.
+
The payload type will be one of message-sending, message-sent, message-delivered, message-failed, message-read or session-started.
+
Note that message-read and session-started callbacks are pertinent only for RBM messages sent from the /messages/multiChannel endpoint.
+
Note that session-started callbacks are pertinent only for RBM messages of agents with the Conversational billing category.
Please visit Webhooks
content: @@ -10015,8 +10220,16 @@ components: groupMmsMessageDeliveredCallback: $ref: >- #/components/examples/groupMmsMessageDeliveredCallbackExample + rbmMessageDeliveredSessionCallback: + $ref: >- + #/components/examples/rbmMessageDeliveredSessionCallbackExample + rbmMessageDeliveredCanInitiateSessionCallback: + $ref: >- + #/components/examples/rbmMessageDeliveredCanInitiateSessionCallbackExample messageFailedCallback: $ref: '#/components/examples/messageFailedCallbackExample' + sessionStartedCallback: + $ref: '#/components/examples/sessionStartedCallbackExample' responses: '200': description: OK diff --git a/bandwidth/__init__.py b/bandwidth/__init__.py index 49558523..e362f656 100644 --- a/bandwidth/__init__.py +++ b/bandwidth/__init__.py @@ -186,6 +186,7 @@ "RbmMessageContentText", "RbmMessageMedia", "RbmOpenUrlEnum", + "RbmSession", "RbmStandaloneCard", "RbmSuggestionResponse", "RbmWebViewEnum", @@ -415,6 +416,7 @@ from bandwidth.models.rbm_message_content_text import RbmMessageContentText as RbmMessageContentText from bandwidth.models.rbm_message_media import RbmMessageMedia as RbmMessageMedia from bandwidth.models.rbm_open_url_enum import RbmOpenUrlEnum as RbmOpenUrlEnum +from bandwidth.models.rbm_session import RbmSession as RbmSession from bandwidth.models.rbm_standalone_card import RbmStandaloneCard as RbmStandaloneCard from bandwidth.models.rbm_suggestion_response import RbmSuggestionResponse as RbmSuggestionResponse from bandwidth.models.rbm_web_view_enum import RbmWebViewEnum as RbmWebViewEnum diff --git a/bandwidth/models/__init__.py b/bandwidth/models/__init__.py index c82403a3..593603e7 100644 --- a/bandwidth/models/__init__.py +++ b/bandwidth/models/__init__.py @@ -160,6 +160,7 @@ from bandwidth.models.rbm_message_content_text import RbmMessageContentText from bandwidth.models.rbm_message_media import RbmMessageMedia from bandwidth.models.rbm_open_url_enum import RbmOpenUrlEnum +from bandwidth.models.rbm_session import RbmSession from bandwidth.models.rbm_standalone_card import RbmStandaloneCard from bandwidth.models.rbm_suggestion_response import RbmSuggestionResponse from bandwidth.models.rbm_web_view_enum import RbmWebViewEnum diff --git a/bandwidth/models/callback.py b/bandwidth/models/callback.py index 1e5fc32a..6d65549c 100644 --- a/bandwidth/models/callback.py +++ b/bandwidth/models/callback.py @@ -54,6 +54,7 @@ class Callback(BaseModel): 'message-sending': 'StatusCallback', 'message-sent': 'StatusCallback', 'requested-location-response': 'InboundCallback', + 'session-started': 'StatusCallback', 'suggestion-response': 'InboundCallback' } diff --git a/bandwidth/models/disconnect_callback.py b/bandwidth/models/disconnect_callback.py index e9091ff2..b43c6bcf 100644 --- a/bandwidth/models/disconnect_callback.py +++ b/bandwidth/models/disconnect_callback.py @@ -47,9 +47,11 @@ class DisconnectCallback(BaseModel): error_id: Optional[StrictStr] = Field(default=None, description="Bandwidth's internal id that references the error event.", alias="errorId") tag: Optional[StrictStr] = Field(default=None, description="(optional) The tag specified on call creation. If no tag was specified or it was previously cleared, this field will not be present.") sip_call_id: Optional[StrictStr] = Field(default=None, description="(optional) The SIP Call-ID of the call's current SIP dialog with Bandwidth's SBC. Used to correlate dialogs and trace calls. Present on any call, inbound or outbound, once that dialog has been established; may be absent very early in a call before the dialog exists.", alias="sipCallId") - sip_response_code: Optional[StrictInt] = Field(default=None, description="(optional) The SIP status code returned by Bandwidth's SBC when it rejected an outbound call's INVITE (e.g. 486 for busy, 603 for decline). Present only when an outbound call was rejected by the SBC.", alias="sipResponseCode") + sip_response_code: Optional[StrictInt] = Field(default=None, description="(optional) The SIP status code of the final response that rejected an outbound call's (or transferred leg's) INVITE (e.g. 486 for busy, 603 for decline). Present only when the call was rejected with a SIP response.", alias="sipResponseCode") + sip_status_line: Optional[StrictStr] = Field(default=None, description="(optional) The full SIP status line, exactly as received, of the final response that rejected an outbound call's INVITE. The reason phrase can help distinguish outcomes that share a status code, such as `603 Network Blocked` and `603 Decline`. Present only when an outbound call was rejected with a SIP response.", alias="sipStatusLine") + sip_headers: Optional[Dict[str, StrictStr]] = Field(default=None, description="(optional) Map of SIP headers, with lowercase keys (SIP headers are case-insensitive). On the Initiate callback, contains the customer-supplied X-* headers from the original INVITE; present only for inbound SIP URI calls with custom headers. On the Disconnect and Transfer Disconnect callbacks, contains a single `reason` key holding the raw, unmodified `Reason` header value from the final SIP response that rejected an outbound call (or transferred leg); present only when that response included a `Reason` header (for example, `v=analytics1` call-analytics block information from a terminating carrier). If the response carries more than one `Reason` header, all values are returned in the order received, separated by `, `. Note - on the Initiate callback, keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. The example shown is for the Initiate callback; on the Disconnect and Transfer Disconnect callbacks the only key is `reason`.", alias="sipHeaders") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["eventType", "eventTime", "accountId", "applicationId", "from", "to", "callId", "direction", "callUrl", "enqueuedTime", "startTime", "answerTime", "endTime", "cause", "errorMessage", "errorId", "tag", "sipCallId", "sipResponseCode"] + __properties: ClassVar[List[str]] = ["eventType", "eventTime", "accountId", "applicationId", "from", "to", "callId", "direction", "callUrl", "enqueuedTime", "startTime", "answerTime", "endTime", "cause", "errorMessage", "errorId", "tag", "sipCallId", "sipResponseCode", "sipStatusLine", "sipHeaders"] model_config = ConfigDict( populate_by_name=True, @@ -152,7 +154,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "errorId": obj.get("errorId"), "tag": obj.get("tag"), "sipCallId": obj.get("sipCallId"), - "sipResponseCode": obj.get("sipResponseCode") + "sipResponseCode": obj.get("sipResponseCode"), + "sipStatusLine": obj.get("sipStatusLine"), + "sipHeaders": obj.get("sipHeaders") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/bandwidth/models/inbound_callback_message.py b/bandwidth/models/inbound_callback_message.py index b42bcbc9..3dec12d7 100644 --- a/bandwidth/models/inbound_callback_message.py +++ b/bandwidth/models/inbound_callback_message.py @@ -26,6 +26,7 @@ from bandwidth.models.multi_channel_message_content import MultiChannelMessageContent from bandwidth.models.priority_enum import PriorityEnum from bandwidth.models.rbm_location_response import RbmLocationResponse +from bandwidth.models.rbm_session import RbmSession from bandwidth.models.rbm_suggestion_response import RbmSuggestionResponse from typing import Optional, Set from typing_extensions import Self @@ -47,11 +48,12 @@ class InboundCallbackMessage(BaseModel): media: Optional[List[StrictStr]] = Field(default=None, description="Optional media, not applicable for sms") priority: Optional[PriorityEnum] = None channel: Optional[MultiChannelMessageChannelEnum] = None + session: Optional[RbmSession] = None content: Optional[MultiChannelMessageContent] = None suggestion_response: Optional[RbmSuggestionResponse] = Field(default=None, alias="suggestionResponse") location_response: Optional[RbmLocationResponse] = Field(default=None, alias="locationResponse") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "owner", "applicationId", "time", "segmentCount", "direction", "to", "from", "text", "tag", "media", "priority", "channel", "content", "suggestionResponse", "locationResponse"] + __properties: ClassVar[List[str]] = ["id", "owner", "applicationId", "time", "segmentCount", "direction", "to", "from", "text", "tag", "media", "priority", "channel", "session", "content", "suggestionResponse", "locationResponse"] model_config = ConfigDict( populate_by_name=True, @@ -94,6 +96,9 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of session + if self.session: + _dict['session'] = self.session.to_dict() # override the default output from pydantic by calling `to_dict()` of content if self.content: _dict['content'] = self.content.to_dict() @@ -133,6 +138,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "media": obj.get("media"), "priority": obj.get("priority"), "channel": obj.get("channel"), + "session": RbmSession.from_dict(obj["session"]) if obj.get("session") is not None else None, "content": MultiChannelMessageContent.from_dict(obj["content"]) if obj.get("content") is not None else None, "suggestionResponse": RbmSuggestionResponse.from_dict(obj["suggestionResponse"]) if obj.get("suggestionResponse") is not None else None, "locationResponse": RbmLocationResponse.from_dict(obj["locationResponse"]) if obj.get("locationResponse") is not None else None diff --git a/bandwidth/models/initiate_callback.py b/bandwidth/models/initiate_callback.py index 4116792b..d0f17531 100644 --- a/bandwidth/models/initiate_callback.py +++ b/bandwidth/models/initiate_callback.py @@ -46,7 +46,7 @@ class InitiateCallback(BaseModel): stir_shaken: Optional[StirShaken] = Field(default=None, alias="stirShaken") uui: Optional[Annotated[str, Field(strict=True, max_length=256)]] = Field(default=None, description="The value of the `User-To-User` header to send within the initial `INVITE`. Must include the encoding parameter as specified in RFC 7433. Only `base64`, `jwt` and `hex` encoding are currently allowed. This value, including the encoding specifier, may not exceed 256 characters.") sip_call_id: Optional[StrictStr] = Field(default=None, description="(optional) The SIP Call-ID of the call's current SIP dialog with Bandwidth's SBC. Used to correlate dialogs and trace calls. Present on any call, inbound or outbound, once that dialog has been established; may be absent very early in a call before the dialog exists.", alias="sipCallId") - sip_headers: Optional[Dict[str, StrictStr]] = Field(default=None, description="(optional) Map of customer-supplied X-* headers from the original INVITE. Keys are lowercase (SIP headers are case-insensitive). Present only for inbound SIP URI calls with custom headers. Note - keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept.", alias="sipHeaders") + sip_headers: Optional[Dict[str, StrictStr]] = Field(default=None, description="(optional) Map of SIP headers, with lowercase keys (SIP headers are case-insensitive). On the Initiate callback, contains the customer-supplied X-* headers from the original INVITE; present only for inbound SIP URI calls with custom headers. On the Disconnect and Transfer Disconnect callbacks, contains a single `reason` key holding the raw, unmodified `Reason` header value from the final SIP response that rejected an outbound call (or transferred leg); present only when that response included a `Reason` header (for example, `v=analytics1` call-analytics block information from a terminating carrier). If the response carries more than one `Reason` header, all values are returned in the order received, separated by `, `. Note - on the Initiate callback, keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. The example shown is for the Initiate callback; on the Disconnect and Transfer Disconnect callbacks the only key is `reason`.", alias="sipHeaders") additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["eventType", "eventTime", "accountId", "applicationId", "from", "to", "direction", "callId", "callUrl", "startTime", "diversion", "stirShaken", "uui", "sipCallId", "sipHeaders"] diff --git a/bandwidth/models/list_message_item.py b/bandwidth/models/list_message_item.py index f0ae8462..9ce88200 100644 --- a/bandwidth/models/list_message_item.py +++ b/bandwidth/models/list_message_item.py @@ -113,6 +113,11 @@ def to_dict(self) -> Dict[str, Any]: if self.message_size is None and "message_size" in self.model_fields_set: _dict['messageSize'] = None + # set to None if message_length (nullable) is None + # and model_fields_set contains the field + if self.message_length is None and "message_length" in self.model_fields_set: + _dict['messageLength'] = None + # set to None if attachment_count (nullable) is None # and model_fields_set contains the field if self.attachment_count is None and "attachment_count" in self.model_fields_set: diff --git a/bandwidth/models/multi_channel_action_calendar_event.py b/bandwidth/models/multi_channel_action_calendar_event.py index bf3f67ae..071fe66c 100644 --- a/bandwidth/models/multi_channel_action_calendar_event.py +++ b/bandwidth/models/multi_channel_action_calendar_event.py @@ -20,7 +20,7 @@ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from typing_extensions import Annotated from bandwidth.models.rbm_action_type_enum import RbmActionTypeEnum from typing import Optional, Set @@ -32,7 +32,7 @@ class MultiChannelActionCalendarEvent(BaseModel): """ # noqa: E501 type: RbmActionTypeEnum text: Annotated[str, Field(strict=True, max_length=25)] = Field(description="Displayed text for user to click") - postback_data: Union[Annotated[bytes, Field(strict=True, max_length=2048)], Annotated[str, Field(strict=True, max_length=2048)]] = Field(description="Base64 payload the customer receives when the reply is clicked.", alias="postbackData") + postback_data: Annotated[str, Field(strict=True, max_length=2048)] = Field(description="Payload the customer receives when the reply is clicked.", alias="postbackData") title: Annotated[str, Field(strict=True, max_length=100)] = Field(description="The title of the event.") start_time: datetime = Field(description="The start time of the event. Must be a valid RFC-3339 value, e.g., 2021-03-14T01:59:26Z or 2021-03-13T20:59:26-05:00.", alias="startTime") end_time: datetime = Field(description="The end time of the event. Must be a valid RFC-3339 value, e.g., 2021-03-14T01:59:26Z or 2021-03-13T20:59:26-05:00.", alias="endTime") diff --git a/bandwidth/models/rbm_action_base.py b/bandwidth/models/rbm_action_base.py index ab5464ab..2693de92 100644 --- a/bandwidth/models/rbm_action_base.py +++ b/bandwidth/models/rbm_action_base.py @@ -19,7 +19,7 @@ import json from pydantic import BaseModel, ConfigDict, Field -from typing import Any, ClassVar, Dict, List, Union +from typing import Any, ClassVar, Dict, List from typing_extensions import Annotated from bandwidth.models.rbm_action_type_enum import RbmActionTypeEnum from typing import Optional, Set @@ -31,7 +31,7 @@ class RbmActionBase(BaseModel): """ # noqa: E501 type: RbmActionTypeEnum text: Annotated[str, Field(strict=True, max_length=25)] = Field(description="Displayed text for user to click") - postback_data: Union[Annotated[bytes, Field(strict=True, max_length=2048)], Annotated[str, Field(strict=True, max_length=2048)]] = Field(description="Base64 payload the customer receives when the reply is clicked.", alias="postbackData") + postback_data: Annotated[str, Field(strict=True, max_length=2048)] = Field(description="Payload the customer receives when the reply is clicked.", alias="postbackData") additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["type", "text", "postbackData"] diff --git a/bandwidth/models/rbm_action_dial.py b/bandwidth/models/rbm_action_dial.py index 45eb04a3..84c0c7a5 100644 --- a/bandwidth/models/rbm_action_dial.py +++ b/bandwidth/models/rbm_action_dial.py @@ -19,7 +19,7 @@ import json from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Union +from typing import Any, ClassVar, Dict, List from typing_extensions import Annotated from bandwidth.models.rbm_action_type_enum import RbmActionTypeEnum from typing import Optional, Set @@ -31,7 +31,7 @@ class RbmActionDial(BaseModel): """ # noqa: E501 type: RbmActionTypeEnum text: Annotated[str, Field(strict=True, max_length=25)] = Field(description="Displayed text for user to click") - postback_data: Union[Annotated[bytes, Field(strict=True, max_length=2048)], Annotated[str, Field(strict=True, max_length=2048)]] = Field(description="Base64 payload the customer receives when the reply is clicked.", alias="postbackData") + postback_data: Annotated[str, Field(strict=True, max_length=2048)] = Field(description="Payload the customer receives when the reply is clicked.", alias="postbackData") phone_number: StrictStr = Field(description="The phone number to dial. Must be E164 format.", alias="phoneNumber") additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["type", "text", "postbackData", "phoneNumber"] diff --git a/bandwidth/models/rbm_action_open_url.py b/bandwidth/models/rbm_action_open_url.py index 0e743910..3bafc55b 100644 --- a/bandwidth/models/rbm_action_open_url.py +++ b/bandwidth/models/rbm_action_open_url.py @@ -19,7 +19,7 @@ import json from pydantic import BaseModel, ConfigDict, Field -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from typing_extensions import Annotated from bandwidth.models.rbm_action_type_enum import RbmActionTypeEnum from bandwidth.models.rbm_open_url_enum import RbmOpenUrlEnum @@ -33,7 +33,7 @@ class RbmActionOpenUrl(BaseModel): """ # noqa: E501 type: RbmActionTypeEnum text: Annotated[str, Field(strict=True, max_length=25)] = Field(description="Displayed text for user to click") - postback_data: Union[Annotated[bytes, Field(strict=True, max_length=2048)], Annotated[str, Field(strict=True, max_length=2048)]] = Field(description="Base64 payload the customer receives when the reply is clicked.", alias="postbackData") + postback_data: Annotated[str, Field(strict=True, max_length=2048)] = Field(description="Payload the customer receives when the reply is clicked.", alias="postbackData") url: Annotated[str, Field(strict=True, max_length=2048)] = Field(description="The URL to open in browser. Must use http:// or https:// scheme.") application: Optional[RbmOpenUrlEnum] = None webview_view_mode: Optional[RbmWebViewEnum] = Field(default=None, alias="webviewViewMode") diff --git a/bandwidth/models/rbm_action_view_location.py b/bandwidth/models/rbm_action_view_location.py index c2708fbe..85105013 100644 --- a/bandwidth/models/rbm_action_view_location.py +++ b/bandwidth/models/rbm_action_view_location.py @@ -19,7 +19,7 @@ import json from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from typing_extensions import Annotated from bandwidth.models.rbm_action_type_enum import RbmActionTypeEnum from typing import Optional, Set @@ -31,7 +31,7 @@ class RbmActionViewLocation(BaseModel): """ # noqa: E501 type: RbmActionTypeEnum text: Annotated[str, Field(strict=True, max_length=25)] = Field(description="Displayed text for user to click") - postback_data: Union[Annotated[bytes, Field(strict=True, max_length=2048)], Annotated[str, Field(strict=True, max_length=2048)]] = Field(description="Base64 payload the customer receives when the reply is clicked.", alias="postbackData") + postback_data: Annotated[str, Field(strict=True, max_length=2048)] = Field(description="Payload the customer receives when the reply is clicked.", alias="postbackData") latitude: StrictStr = Field(description="The latitude of the location. Must be in range [-90.000000, 90.000000].") longitude: StrictStr = Field(description="The longitude of the location. Must be in range [-180.000000, 180.000000].") label: Optional[Annotated[str, Field(strict=True, max_length=100)]] = Field(default=None, description="The label of the location.") diff --git a/bandwidth/models/rbm_session.py b/bandwidth/models/rbm_session.py new file mode 100644 index 00000000..e56589f4 --- /dev/null +++ b/bandwidth/models/rbm_session.py @@ -0,0 +1,130 @@ +# coding: utf-8 + +""" + Bandwidth + + Bandwidth's Communication APIs + + The version of the OpenAPI document: 1.0.0 + Contact: letstalk@bandwidth.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self + +class RbmSession(BaseModel): + """ + RCS interactive session information for the message. An interactive session is a 24-hour window covering all Rich and Rich Media messages exchanged between an RCS agent and a specific end user mobile number. It is triggered when at least 4 messages are exchanged within 24 hours, at least 2 of them mobile-originated and at least 1 mobile-terminated. Only present on `message-delivered`, `message-received` and `session-started` callbacks for RBM messages of US agents with the Conversational billing category. Omitted for all other messages. + """ # noqa: E501 + can_initiate: StrictBool = Field(description="Whether the message can start an interactive session. `false` if the message is already part of an active session, or if the agent is not eligible for interactive session billing.", alias="canInitiate") + id: Optional[StrictStr] = Field(default=None, description="The unique identifier of the interactive session the message is part of. This is not a message ID. Omitted if the message is not part of a session.") + type: Optional[StrictStr] = Field(default=None, description="The type of the interactive session. Only present on `session-started` callbacks.") + start_time: Optional[datetime] = Field(default=None, description="The time the 24-hour session window opened. Only present on `session-started` callbacks.", alias="startTime") + end_time: Optional[datetime] = Field(default=None, description="The time the 24-hour session window closes. Only present on `session-started` callbacks.", alias="endTime") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["canInitiate", "id", "type", "startTime", "endTime"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of RbmSession from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + # set to None if id (nullable) is None + # and model_fields_set contains the field + if self.id is None and "id" in self.model_fields_set: + _dict['id'] = None + + # set to None if type (nullable) is None + # and model_fields_set contains the field + if self.type is None and "type" in self.model_fields_set: + _dict['type'] = None + + # set to None if start_time (nullable) is None + # and model_fields_set contains the field + if self.start_time is None and "start_time" in self.model_fields_set: + _dict['startTime'] = None + + # set to None if end_time (nullable) is None + # and model_fields_set contains the field + if self.end_time is None and "end_time" in self.model_fields_set: + _dict['endTime'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of RbmSession from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "canInitiate": obj.get("canInitiate"), + "id": obj.get("id"), + "type": obj.get("type"), + "startTime": obj.get("startTime"), + "endTime": obj.get("endTime") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/bandwidth/models/rbm_suggestion_response.py b/bandwidth/models/rbm_suggestion_response.py index a0f4c30a..69b3047a 100644 --- a/bandwidth/models/rbm_suggestion_response.py +++ b/bandwidth/models/rbm_suggestion_response.py @@ -19,7 +19,7 @@ import json from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from typing_extensions import Annotated from typing import Optional, Set from typing_extensions import Self @@ -28,8 +28,8 @@ class RbmSuggestionResponse(BaseModel): """ RbmSuggestionResponse """ # noqa: E501 - text: Optional[StrictStr] = Field(default=None, description="The text associated with the suggestion response.") - postback_data: Optional[Union[Annotated[bytes, Field(strict=True, max_length=2048)], Annotated[str, Field(strict=True, max_length=2048)]]] = Field(default=None, description="Base64 payload the customer receives when the reply is clicked.", alias="postbackData") + text: Optional[StrictStr] = Field(default=None, description="The text of the suggestion the user tapped.") + postback_data: Optional[Annotated[str, Field(strict=True, max_length=2048)]] = Field(default=None, description="Payload the customer receives when the reply is clicked.", alias="postbackData") paired_message_id: Optional[StrictStr] = Field(default=None, description="Corresponding parent message ID (MT).", alias="pairedMessageId") additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["text", "postbackData", "pairedMessageId"] diff --git a/bandwidth/models/status_callback_message.py b/bandwidth/models/status_callback_message.py index ba3acaea..58f4ee3b 100644 --- a/bandwidth/models/status_callback_message.py +++ b/bandwidth/models/status_callback_message.py @@ -24,6 +24,7 @@ from bandwidth.models.message_direction_enum import MessageDirectionEnum from bandwidth.models.multi_channel_message_channel_enum import MultiChannelMessageChannelEnum from bandwidth.models.priority_enum import PriorityEnum +from bandwidth.models.rbm_session import RbmSession from typing import Optional, Set from typing_extensions import Self @@ -44,8 +45,9 @@ class StatusCallbackMessage(BaseModel): media: Optional[List[StrictStr]] = Field(default=None, description="Optional media, not applicable for sms") priority: Optional[PriorityEnum] = None channel: Optional[MultiChannelMessageChannelEnum] = None + session: Optional[RbmSession] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "owner", "applicationId", "time", "segmentCount", "direction", "to", "from", "text", "tag", "media", "priority", "channel"] + __properties: ClassVar[List[str]] = ["id", "owner", "applicationId", "time", "segmentCount", "direction", "to", "from", "text", "tag", "media", "priority", "channel", "session"] model_config = ConfigDict( populate_by_name=True, @@ -88,6 +90,9 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of session + if self.session: + _dict['session'] = self.session.to_dict() # puts key-value pairs in additional_properties in the top level if self.additional_properties is not None: for _key, _value in self.additional_properties.items(): @@ -117,7 +122,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "tag": obj.get("tag"), "media": obj.get("media"), "priority": obj.get("priority"), - "channel": obj.get("channel") + "channel": obj.get("channel"), + "session": RbmSession.from_dict(obj["session"]) if obj.get("session") is not None else None }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/bandwidth/models/status_callback_type_enum.py b/bandwidth/models/status_callback_type_enum.py index 79858c72..9fe5edd9 100644 --- a/bandwidth/models/status_callback_type_enum.py +++ b/bandwidth/models/status_callback_type_enum.py @@ -21,7 +21,7 @@ class StatusCallbackTypeEnum(str, Enum): """ - The possible status callbacks when sending an MT SMS or MMS or RBM message: - `message-sending` indicates that Bandwidth is sending the message to the upstream provider. - `message-sent` indicates that the message has left the Bandwidth network and is in transit to the provider. - `message-delivered` indicates that the message was successfully delivered to handset. - `message-failed` indicates that the message could not be sent to the intended recipient. - `message-read` indicates that the RBM message was read by the recipient. + The possible status callbacks when sending an MT SMS or MMS or RBM message: - `message-sending` indicates that Bandwidth is sending the message to the upstream provider. - `message-sent` indicates that the message has left the Bandwidth network and is in transit to the provider. - `message-delivered` indicates that the message was successfully delivered to handset. - `message-failed` indicates that the message could not be sent to the intended recipient. - `message-read` indicates that the RBM message was read by the recipient. - `session-started` indicates that a previously exchanged RBM message is now part of an RCS interactive session and is no longer billed individually. """ """ @@ -32,6 +32,7 @@ class StatusCallbackTypeEnum(str, Enum): MESSAGE_MINUS_DELIVERED = 'message-delivered' MESSAGE_MINUS_FAILED = 'message-failed' MESSAGE_MINUS_READ = 'message-read' + SESSION_MINUS_STARTED = 'session-started' @classmethod def from_json(cls, json_str: str) -> Self: diff --git a/bandwidth/models/transfer_disconnect_callback.py b/bandwidth/models/transfer_disconnect_callback.py index 60e655df..20411178 100644 --- a/bandwidth/models/transfer_disconnect_callback.py +++ b/bandwidth/models/transfer_disconnect_callback.py @@ -19,7 +19,7 @@ import json from datetime import datetime -from pydantic import BaseModel, ConfigDict, Field, StrictStr +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr from typing import Any, ClassVar, Dict, List, Optional from bandwidth.models.call_direction_enum import CallDirectionEnum from typing import Optional, Set @@ -49,8 +49,11 @@ class TransferDisconnectCallback(BaseModel): cause: Optional[StrictStr] = Field(default=None, description="Reason the call failed - hangup, busy, timeout, cancel, rejected, callback-error, invalid-bxml, application-error, account-limit, node-capacity-exceeded, error, or unknown.") error_message: Optional[StrictStr] = Field(default=None, description="Text explaining the reason that caused the call to fail in case of errors.", alias="errorMessage") error_id: Optional[StrictStr] = Field(default=None, description="Bandwidth's internal id that references the error event.", alias="errorId") + sip_response_code: Optional[StrictInt] = Field(default=None, description="(optional) The SIP status code of the final response that rejected an outbound call's (or transferred leg's) INVITE (e.g. 486 for busy, 603 for decline). Present only when the call was rejected with a SIP response.", alias="sipResponseCode") + sip_status_line: Optional[StrictStr] = Field(default=None, description="(optional) The full SIP status line, exactly as received, of the final response that rejected an outbound call's INVITE. The reason phrase can help distinguish outcomes that share a status code, such as `603 Network Blocked` and `603 Decline`. Present only when an outbound call was rejected with a SIP response.", alias="sipStatusLine") + sip_headers: Optional[Dict[str, StrictStr]] = Field(default=None, description="(optional) Map of SIP headers, with lowercase keys (SIP headers are case-insensitive). On the Initiate callback, contains the customer-supplied X-* headers from the original INVITE; present only for inbound SIP URI calls with custom headers. On the Disconnect and Transfer Disconnect callbacks, contains a single `reason` key holding the raw, unmodified `Reason` header value from the final SIP response that rejected an outbound call (or transferred leg); present only when that response included a `Reason` header (for example, `v=analytics1` call-analytics block information from a terminating carrier). If the response carries more than one `Reason` header, all values are returned in the order received, separated by `, `. Note - on the Initiate callback, keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. The example shown is for the Initiate callback; on the Disconnect and Transfer Disconnect callbacks the only key is `reason`.", alias="sipHeaders") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["eventType", "eventTime", "accountId", "applicationId", "from", "to", "direction", "callId", "callUrl", "parentCallId", "enqueuedTime", "startTime", "answerTime", "endTime", "tag", "transferCallerId", "transferTo", "cause", "errorMessage", "errorId"] + __properties: ClassVar[List[str]] = ["eventType", "eventTime", "accountId", "applicationId", "from", "to", "direction", "callId", "callUrl", "parentCallId", "enqueuedTime", "startTime", "answerTime", "endTime", "tag", "transferCallerId", "transferTo", "cause", "errorMessage", "errorId", "sipResponseCode", "sipStatusLine", "sipHeaders"] model_config = ConfigDict( populate_by_name=True, @@ -154,7 +157,10 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "transferTo": obj.get("transferTo"), "cause": obj.get("cause"), "errorMessage": obj.get("errorMessage"), - "errorId": obj.get("errorId") + "errorId": obj.get("errorId"), + "sipResponseCode": obj.get("sipResponseCode"), + "sipStatusLine": obj.get("sipStatusLine"), + "sipHeaders": obj.get("sipHeaders") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/docs/DisconnectCallback.md b/docs/DisconnectCallback.md index dacdfa75..db407c4f 100644 --- a/docs/DisconnectCallback.md +++ b/docs/DisconnectCallback.md @@ -24,7 +24,9 @@ Name | Type | Description | Notes **error_id** | **str** | Bandwidth's internal id that references the error event. | [optional] **tag** | **str** | (optional) The tag specified on call creation. If no tag was specified or it was previously cleared, this field will not be present. | [optional] **sip_call_id** | **str** | (optional) The SIP Call-ID of the call's current SIP dialog with Bandwidth's SBC. Used to correlate dialogs and trace calls. Present on any call, inbound or outbound, once that dialog has been established; may be absent very early in a call before the dialog exists. | [optional] -**sip_response_code** | **int** | (optional) The SIP status code returned by Bandwidth's SBC when it rejected an outbound call's INVITE (e.g. 486 for busy, 603 for decline). Present only when an outbound call was rejected by the SBC. | [optional] +**sip_response_code** | **int** | (optional) The SIP status code of the final response that rejected an outbound call's (or transferred leg's) INVITE (e.g. 486 for busy, 603 for decline). Present only when the call was rejected with a SIP response. | [optional] +**sip_status_line** | **str** | (optional) The full SIP status line, exactly as received, of the final response that rejected an outbound call's INVITE. The reason phrase can help distinguish outcomes that share a status code, such as `603 Network Blocked` and `603 Decline`. Present only when an outbound call was rejected with a SIP response. | [optional] +**sip_headers** | **Dict[str, str]** | (optional) Map of SIP headers, with lowercase keys (SIP headers are case-insensitive). On the Initiate callback, contains the customer-supplied X-* headers from the original INVITE; present only for inbound SIP URI calls with custom headers. On the Disconnect and Transfer Disconnect callbacks, contains a single `reason` key holding the raw, unmodified `Reason` header value from the final SIP response that rejected an outbound call (or transferred leg); present only when that response included a `Reason` header (for example, `v=analytics1` call-analytics block information from a terminating carrier). If the response carries more than one `Reason` header, all values are returned in the order received, separated by `, `. Note - on the Initiate callback, keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. The example shown is for the Initiate callback; on the Disconnect and Transfer Disconnect callbacks the only key is `reason`. | [optional] ## Example diff --git a/docs/InboundCallbackMessage.md b/docs/InboundCallbackMessage.md index 5a46c208..31ae294f 100644 --- a/docs/InboundCallbackMessage.md +++ b/docs/InboundCallbackMessage.md @@ -18,6 +18,7 @@ Name | Type | Description | Notes **media** | **List[str]** | Optional media, not applicable for sms | [optional] **priority** | [**PriorityEnum**](PriorityEnum.md) | | [optional] **channel** | [**MultiChannelMessageChannelEnum**](MultiChannelMessageChannelEnum.md) | | [optional] +**session** | [**RbmSession**](RbmSession.md) | | [optional] **content** | [**MultiChannelMessageContent**](MultiChannelMessageContent.md) | | [optional] **suggestion_response** | [**RbmSuggestionResponse**](RbmSuggestionResponse.md) | | [optional] **location_response** | [**RbmLocationResponse**](RbmLocationResponse.md) | | [optional] diff --git a/docs/InitiateCallback.md b/docs/InitiateCallback.md index 02199ebe..8137e8e7 100644 --- a/docs/InitiateCallback.md +++ b/docs/InitiateCallback.md @@ -20,7 +20,7 @@ Name | Type | Description | Notes **stir_shaken** | [**StirShaken**](StirShaken.md) | | [optional] **uui** | **str** | The value of the `User-To-User` header to send within the initial `INVITE`. Must include the encoding parameter as specified in RFC 7433. Only `base64`, `jwt` and `hex` encoding are currently allowed. This value, including the encoding specifier, may not exceed 256 characters. | [optional] **sip_call_id** | **str** | (optional) The SIP Call-ID of the call's current SIP dialog with Bandwidth's SBC. Used to correlate dialogs and trace calls. Present on any call, inbound or outbound, once that dialog has been established; may be absent very early in a call before the dialog exists. | [optional] -**sip_headers** | **Dict[str, str]** | (optional) Map of customer-supplied X-* headers from the original INVITE. Keys are lowercase (SIP headers are case-insensitive). Present only for inbound SIP URI calls with custom headers. Note - keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. | [optional] +**sip_headers** | **Dict[str, str]** | (optional) Map of SIP headers, with lowercase keys (SIP headers are case-insensitive). On the Initiate callback, contains the customer-supplied X-* headers from the original INVITE; present only for inbound SIP URI calls with custom headers. On the Disconnect and Transfer Disconnect callbacks, contains a single `reason` key holding the raw, unmodified `Reason` header value from the final SIP response that rejected an outbound call (or transferred leg); present only when that response included a `Reason` header (for example, `v=analytics1` call-analytics block information from a terminating carrier). If the response carries more than one `Reason` header, all values are returned in the order received, separated by `, `. Note - on the Initiate callback, keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. The example shown is for the Initiate callback; on the Disconnect and Transfer Disconnect callbacks the only key is `reason`. | [optional] ## Example diff --git a/docs/MultiChannelAction.md b/docs/MultiChannelAction.md index 5900a198..04ce7aa3 100644 --- a/docs/MultiChannelAction.md +++ b/docs/MultiChannelAction.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **type** | [**RbmActionTypeEnum**](RbmActionTypeEnum.md) | | **text** | **str** | Displayed text for user to click | -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | **phone_number** | **str** | The phone number to dial. Must be E164 format. | **latitude** | **float** | The latitude of the location. Must be in range [-90.000000, 90.000000]. | **longitude** | **float** | The longitude of the location. Must be in range [-180.000000, 180.000000]. | diff --git a/docs/MultiChannelActionCalendarEvent.md b/docs/MultiChannelActionCalendarEvent.md index 4ded4b68..685d6f9b 100644 --- a/docs/MultiChannelActionCalendarEvent.md +++ b/docs/MultiChannelActionCalendarEvent.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **type** | [**RbmActionTypeEnum**](RbmActionTypeEnum.md) | | **text** | **str** | Displayed text for user to click | -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | **title** | **str** | The title of the event. | **start_time** | **datetime** | The start time of the event. Must be a valid RFC-3339 value, e.g., 2021-03-14T01:59:26Z or 2021-03-13T20:59:26-05:00. | **end_time** | **datetime** | The end time of the event. Must be a valid RFC-3339 value, e.g., 2021-03-14T01:59:26Z or 2021-03-13T20:59:26-05:00. | diff --git a/docs/RbmActionBase.md b/docs/RbmActionBase.md index 7c946cc6..ab3941e7 100644 --- a/docs/RbmActionBase.md +++ b/docs/RbmActionBase.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **type** | [**RbmActionTypeEnum**](RbmActionTypeEnum.md) | | **text** | **str** | Displayed text for user to click | -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | ## Example diff --git a/docs/RbmActionDial.md b/docs/RbmActionDial.md index 56bba04f..1e386fa9 100644 --- a/docs/RbmActionDial.md +++ b/docs/RbmActionDial.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **type** | [**RbmActionTypeEnum**](RbmActionTypeEnum.md) | | **text** | **str** | Displayed text for user to click | -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | **phone_number** | **str** | The phone number to dial. Must be E164 format. | ## Example diff --git a/docs/RbmActionOpenUrl.md b/docs/RbmActionOpenUrl.md index d71fd352..fb78e2a0 100644 --- a/docs/RbmActionOpenUrl.md +++ b/docs/RbmActionOpenUrl.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **type** | [**RbmActionTypeEnum**](RbmActionTypeEnum.md) | | **text** | **str** | Displayed text for user to click | -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | **url** | **str** | The URL to open in browser. Must use http:// or https:// scheme. | **application** | [**RbmOpenUrlEnum**](RbmOpenUrlEnum.md) | | [optional] **webview_view_mode** | [**RbmWebViewEnum**](RbmWebViewEnum.md) | | [optional] diff --git a/docs/RbmActionViewLocation.md b/docs/RbmActionViewLocation.md index 60d2c4ef..d5cb799f 100644 --- a/docs/RbmActionViewLocation.md +++ b/docs/RbmActionViewLocation.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **type** | [**RbmActionTypeEnum**](RbmActionTypeEnum.md) | | **text** | **str** | Displayed text for user to click | -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | **latitude** | **float** | The latitude of the location. Must be in range [-90.000000, 90.000000]. | **longitude** | **float** | The longitude of the location. Must be in range [-180.000000, 180.000000]. | **label** | **str** | The label of the location. | [optional] diff --git a/docs/RbmSession.md b/docs/RbmSession.md new file mode 100644 index 00000000..f80b509a --- /dev/null +++ b/docs/RbmSession.md @@ -0,0 +1,34 @@ +# RbmSession + +RCS interactive session information for the message. An interactive session is a 24-hour window covering all Rich and Rich Media messages exchanged between an RCS agent and a specific end user mobile number. It is triggered when at least 4 messages are exchanged within 24 hours, at least 2 of them mobile-originated and at least 1 mobile-terminated. Only present on `message-delivered`, `message-received` and `session-started` callbacks for RBM messages of US agents with the Conversational billing category. Omitted for all other messages. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**can_initiate** | **bool** | Whether the message can start an interactive session. `false` if the message is already part of an active session, or if the agent is not eligible for interactive session billing. | +**id** | **str** | The unique identifier of the interactive session the message is part of. This is not a message ID. Omitted if the message is not part of a session. | [optional] +**type** | **str** | The type of the interactive session. Only present on `session-started` callbacks. | [optional] +**start_time** | **datetime** | The time the 24-hour session window opened. Only present on `session-started` callbacks. | [optional] +**end_time** | **datetime** | The time the 24-hour session window closes. Only present on `session-started` callbacks. | [optional] + +## Example + +```python +from bandwidth.models.rbm_session import RbmSession + +# TODO update the JSON string below +json = "{}" +# create an instance of RbmSession from a JSON string +rbm_session_instance = RbmSession.from_json(json) +# print the JSON string representation of the object +print(RbmSession.to_json()) + +# convert the object into a dict +rbm_session_dict = rbm_session_instance.to_dict() +# create an instance of RbmSession from a dict +rbm_session_from_dict = RbmSession.from_dict(rbm_session_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/RbmSuggestionResponse.md b/docs/RbmSuggestionResponse.md index 40d80934..e88584e7 100644 --- a/docs/RbmSuggestionResponse.md +++ b/docs/RbmSuggestionResponse.md @@ -5,8 +5,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**text** | **str** | The text associated with the suggestion response. | [optional] -**postback_data** | **bytearray** | Base64 payload the customer receives when the reply is clicked. | [optional] +**text** | **str** | The text of the suggestion the user tapped. | [optional] +**postback_data** | **str** | Payload the customer receives when the reply is clicked. | [optional] **paired_message_id** | **str** | Corresponding parent message ID (MT). | [optional] ## Example diff --git a/docs/StatusCallbackMessage.md b/docs/StatusCallbackMessage.md index b65dddc9..f3eeb03c 100644 --- a/docs/StatusCallbackMessage.md +++ b/docs/StatusCallbackMessage.md @@ -19,6 +19,7 @@ Name | Type | Description | Notes **media** | **List[str]** | Optional media, not applicable for sms | [optional] **priority** | [**PriorityEnum**](PriorityEnum.md) | | [optional] **channel** | [**MultiChannelMessageChannelEnum**](MultiChannelMessageChannelEnum.md) | | [optional] +**session** | [**RbmSession**](RbmSession.md) | | [optional] ## Example diff --git a/docs/StatusCallbackTypeEnum.md b/docs/StatusCallbackTypeEnum.md index 17d4a78e..e7317afa 100644 --- a/docs/StatusCallbackTypeEnum.md +++ b/docs/StatusCallbackTypeEnum.md @@ -1,6 +1,6 @@ # StatusCallbackTypeEnum -The possible status callbacks when sending an MT SMS or MMS or RBM message: - `message-sending` indicates that Bandwidth is sending the message to the upstream provider. - `message-sent` indicates that the message has left the Bandwidth network and is in transit to the provider. - `message-delivered` indicates that the message was successfully delivered to handset. - `message-failed` indicates that the message could not be sent to the intended recipient. - `message-read` indicates that the RBM message was read by the recipient. +The possible status callbacks when sending an MT SMS or MMS or RBM message: - `message-sending` indicates that Bandwidth is sending the message to the upstream provider. - `message-sent` indicates that the message has left the Bandwidth network and is in transit to the provider. - `message-delivered` indicates that the message was successfully delivered to handset. - `message-failed` indicates that the message could not be sent to the intended recipient. - `message-read` indicates that the RBM message was read by the recipient. - `session-started` indicates that a previously exchanged RBM message is now part of an RCS interactive session and is no longer billed individually. ## Enum @@ -14,6 +14,8 @@ The possible status callbacks when sending an MT SMS or MMS or RBM message: - `m * `MESSAGE_MINUS_READ` (value: `'message-read'`) +* `SESSION_MINUS_STARTED` (value: `'session-started'`) + [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/docs/TransferDisconnectCallback.md b/docs/TransferDisconnectCallback.md index bf1313c9..9220ca1a 100644 --- a/docs/TransferDisconnectCallback.md +++ b/docs/TransferDisconnectCallback.md @@ -26,6 +26,9 @@ Name | Type | Description | Notes **cause** | **str** | Reason the call failed - hangup, busy, timeout, cancel, rejected, callback-error, invalid-bxml, application-error, account-limit, node-capacity-exceeded, error, or unknown. | [optional] **error_message** | **str** | Text explaining the reason that caused the call to fail in case of errors. | [optional] **error_id** | **str** | Bandwidth's internal id that references the error event. | [optional] +**sip_response_code** | **int** | (optional) The SIP status code of the final response that rejected an outbound call's (or transferred leg's) INVITE (e.g. 486 for busy, 603 for decline). Present only when the call was rejected with a SIP response. | [optional] +**sip_status_line** | **str** | (optional) The full SIP status line, exactly as received, of the final response that rejected an outbound call's INVITE. The reason phrase can help distinguish outcomes that share a status code, such as `603 Network Blocked` and `603 Decline`. Present only when an outbound call was rejected with a SIP response. | [optional] +**sip_headers** | **Dict[str, str]** | (optional) Map of SIP headers, with lowercase keys (SIP headers are case-insensitive). On the Initiate callback, contains the customer-supplied X-* headers from the original INVITE; present only for inbound SIP URI calls with custom headers. On the Disconnect and Transfer Disconnect callbacks, contains a single `reason` key holding the raw, unmodified `Reason` header value from the final SIP response that rejected an outbound call (or transferred leg); present only when that response included a `Reason` header (for example, `v=analytics1` call-analytics block information from a terminating carrier). If the response carries more than one `Reason` header, all values are returned in the order received, separated by `, `. Note - on the Initiate callback, keys preserve the original SIP header name in lowercase rather than Bandwidth's usual camelCase JSON convention, since these are passthrough values from the caller's SIP INVITE, not Bandwidth-defined fields. If the same header name is sent more than once in the INVITE, only the last value is kept. The example shown is for the Initiate callback; on the Disconnect and Transfer Disconnect callbacks the only key is `reason`. | [optional] ## Example diff --git a/test/smoke/test_multi_channel_api.py b/test/smoke/test_multi_channel_api.py index 20ab1073..2d65dfa1 100644 --- a/test/smoke/test_multi_channel_api.py +++ b/test/smoke/test_multi_channel_api.py @@ -255,15 +255,13 @@ def test_create_multi_channel_rbm_text_message(self) -> None: assert_that(rbm_content.suggestions, instance_of(list)) assert_that(len(rbm_content.suggestions), equal_to(1)) assert_that(rbm_content.suggestions[0], instance_of(MultiChannelAction)) - - # skip below for now because python doesn't respect discriminator field properly - # action = rbm_content.suggestions[0] - # assert_that(action.actual_instance, instance_of(RbmActionDial)) - # dial_action = action.actual_instance - # assert_that(dial_action.type, equal_to(RbmActionTypeEnum.DIAL_PHONE)) - # assert_that(dial_action.text, equal_to("Call Us")) - # assert_that(dial_action.postback_data, equal_to('U0dWc2JHOGdkMjl5YkdRPQ==')) - # assert_that(dial_action.phone_number, equal_to(BW_NUMBER)) + action = rbm_content.suggestions[0] + assert_that(action.actual_instance, instance_of(RbmActionDial)) + dial_action = action.actual_instance + assert_that(dial_action.type, equal_to(RbmActionTypeEnum.DIAL_PHONE)) + assert_that(dial_action.text, equal_to("Call Us")) + assert_that(dial_action.postback_data, equal_to('U0dWc2JHOGdkMjl5YkdRPQ==')) + assert_that(dial_action.phone_number, equal_to(BW_NUMBER)) def test_create_multi_channel_rbm_media_message(self) -> None: """Test case for create_multi_channel_message @@ -342,15 +340,13 @@ def test_create_multi_channel_rbm_media_message(self) -> None: assert_that(rbm_content.suggestions, instance_of(list)) assert_that(len(rbm_content.suggestions), equal_to(1)) assert_that(rbm_content.suggestions[0], instance_of(MultiChannelAction)) - - # skip below for now because python doesn't respect discriminator field properly - # action = rbm_content.suggestions[0] - # assert_that(action.actual_instance, instance_of(RbmActionDial)) - # dial_action = action.actual_instance - # assert_that(dial_action.type, equal_to(RbmActionTypeEnum.DIAL_PHONE)) - # assert_that(dial_action.text, equal_to("Call Us")) - # assert_that(dial_action.postback_data, equal_to('U0dWc2JHOGdkMjl5YkdRPQ==')) - # assert_that(dial_action.phone_number, equal_to(BW_NUMBER)) + action = rbm_content.suggestions[0] + assert_that(action.actual_instance, instance_of(RbmActionDial)) + dial_action = action.actual_instance + assert_that(dial_action.type, equal_to(RbmActionTypeEnum.DIAL_PHONE)) + assert_that(dial_action.text, equal_to("Call Us")) + assert_that(dial_action.postback_data, equal_to('U0dWc2JHOGdkMjl5YkdRPQ==')) + assert_that(dial_action.phone_number, equal_to(BW_NUMBER)) def test_create_multi_channel_rbm_rich_standalone_message(self) -> None: """Test case for create_multi_channel_message diff --git a/test/unit/models/test_disconnect_callback.py b/test/unit/models/test_disconnect_callback.py index 23184234..a98e3e07 100644 --- a/test/unit/models/test_disconnect_callback.py +++ b/test/unit/models/test_disconnect_callback.py @@ -52,7 +52,9 @@ def make_instance(self, include_optional) -> DisconnectCallback: error_id = '4642074b-7b58-478b-96e4-3a60955c6765', tag = 'exampleTag', sip_call_id = 'sipCallIdExample', - sip_response_code = 200 + sip_response_code = 603, + sip_status_line = 'SIP/2.0 603 Network Blocked', + sip_headers = {'reason': 'v=analytics1'} ) else: return DisconnectCallback( @@ -81,7 +83,9 @@ def testDisconnectCallback(self): assert instance.error_id == '4642074b-7b58-478b-96e4-3a60955c6765' assert instance.tag == 'exampleTag' assert instance.sip_call_id == 'sipCallIdExample' - assert instance.sip_response_code == 200 + assert instance.sip_response_code == 603 + assert instance.sip_status_line == 'SIP/2.0 603 Network Blocked' + assert instance.sip_headers == {'reason': 'v=analytics1'} if __name__ == '__main__': unittest.main() diff --git a/test/unit/models/test_inbound_callback_message.py b/test/unit/models/test_inbound_callback_message.py index d57b43ec..0aa74916 100644 --- a/test/unit/models/test_inbound_callback_message.py +++ b/test/unit/models/test_inbound_callback_message.py @@ -19,6 +19,7 @@ from bandwidth.models.inbound_callback_message import InboundCallbackMessage from bandwidth.models.multi_channel_message_content import MultiChannelMessageContent from bandwidth.models.rbm_location_response import RbmLocationResponse +from bandwidth.models.rbm_session import RbmSession from bandwidth.models.rbm_suggestion_response import RbmSuggestionResponse from bandwidth.models.rbm_message_content_file import RbmMessageContentFile @@ -51,6 +52,12 @@ def make_instance(self, include_optional) -> InboundCallbackMessage: media = ['https://dev.bandwidth.com/images/bandwidth-logo.png', 'https://dev.bandwidth.com/images/github_logo.png'], priority = 'default', channel = 'RBM', + session = RbmSession( + can_initiate = False, + id = '0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d', + type = 'INTERACTIVE_SESSION', + start_time = '2026-08-06T09:59:55Z', + end_time = '2026-08-07T09:59:55Z', ), content = MultiChannelMessageContent( text = '', media = RbmMessageContentFile( @@ -58,7 +65,7 @@ def make_instance(self, include_optional) -> InboundCallbackMessage: thumbnail_url = 'https://dev.bandwidth.com/images/bandwidth-logo.png', ), ), suggestion_response = RbmSuggestionResponse( text = 'Yes, I would like to proceed', - postback_data = '[B@5d4a34ff', ), + postback_data = 'postbackData', ), location_response = RbmLocationResponse( latitude = '37.7749', longitude = '-122.4194', ) @@ -92,6 +99,7 @@ def testInboundCallbackMessage(self): assert instance.tag == 'custom string' assert isinstance(instance.media, list) assert len(instance.media) > 0 + assert isinstance(instance.session, RbmSession) assert isinstance(instance.content, MultiChannelMessageContent) assert isinstance(instance.suggestion_response, RbmSuggestionResponse) assert isinstance(instance.location_response, RbmLocationResponse) diff --git a/test/unit/models/test_multi_channel_action.py b/test/unit/models/test_multi_channel_action.py index 72e2fe5a..c8b189f7 100644 --- a/test/unit/models/test_multi_channel_action.py +++ b/test/unit/models/test_multi_channel_action.py @@ -42,18 +42,18 @@ def testMultiChannelAction(self): model_rbm_action_base = MultiChannelAction(RbmActionBase( type='REPLY', text='Hello world', - postback_data='[B@32298473' + postback_data='postbackData' )) model_rbm_action_dial = MultiChannelAction(RbmActionDial( type='DIAL_PHONE', text='Hello world', - postback_data='[B@32298473', + postback_data='postbackData', phone_number='1234567890' )) model_rbm_action_view_location = MultiChannelAction(RbmActionViewLocation( type='SHOW_LOCATION', text='Hello world', - postback_data='[B@32298473', + postback_data='postbackData', latitude='37.7749', longitude='-122.4194', label='San Francisco' @@ -70,7 +70,7 @@ def testMultiChannelAction(self): model_rbm_action_open_url = MultiChannelAction(RbmActionOpenUrl( type='OPEN_URL', text='Hello world', - postback_data='[B@32298473', + postback_data='postbackData', url='https://www.example.com' )) diff --git a/test/unit/models/test_multi_channel_channel_list_rbm_object_all_of_content.py b/test/unit/models/test_multi_channel_channel_list_rbm_object_all_of_content.py index 3629f6fe..ea9de86f 100644 --- a/test/unit/models/test_multi_channel_channel_list_rbm_object_all_of_content.py +++ b/test/unit/models/test_multi_channel_channel_list_rbm_object_all_of_content.py @@ -52,7 +52,7 @@ def testMultiChannelChannelListRBMObjectAllOfContent(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -67,7 +67,7 @@ def testMultiChannelChannelListRBMObjectAllOfContent(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -89,7 +89,7 @@ def testMultiChannelChannelListRBMObjectAllOfContent(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -99,7 +99,7 @@ def testMultiChannelChannelListRBMObjectAllOfContent(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_action_base.py b/test/unit/models/test_rbm_action_base.py index e67e750a..c84a84ab 100644 --- a/test/unit/models/test_rbm_action_base.py +++ b/test/unit/models/test_rbm_action_base.py @@ -35,13 +35,13 @@ def make_instance(self, include_optional) -> RbmActionBase: return RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) else: return RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473', + postback_data = 'postbackData', ) def testRbmActionBase(self): @@ -51,7 +51,7 @@ def testRbmActionBase(self): assert isinstance(instance, RbmActionBase) assert instance.type == 'REPLY' assert instance.text == 'Hello world' - assert instance.postback_data == '[B@32298473' + assert instance.postback_data == 'postbackData' if __name__ == '__main__': unittest.main() diff --git a/test/unit/models/test_rbm_card_content.py b/test/unit/models/test_rbm_card_content.py index a06164a7..f142314f 100644 --- a/test/unit/models/test_rbm_card_content.py +++ b/test/unit/models/test_rbm_card_content.py @@ -48,7 +48,7 @@ def make_instance(self, include_optional) -> RbmCardContent: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_message_carousel_card.py b/test/unit/models/test_rbm_message_carousel_card.py index 66a12c12..8293545f 100644 --- a/test/unit/models/test_rbm_message_carousel_card.py +++ b/test/unit/models/test_rbm_message_carousel_card.py @@ -52,7 +52,7 @@ def make_instance(self, include_optional) -> RbmMessageCarouselCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ], @@ -70,7 +70,7 @@ def make_instance(self, include_optional) -> RbmMessageCarouselCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ], @@ -81,7 +81,7 @@ def make_instance(self, include_optional) -> RbmMessageCarouselCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -103,7 +103,7 @@ def make_instance(self, include_optional) -> RbmMessageCarouselCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ], @@ -121,7 +121,7 @@ def make_instance(self, include_optional) -> RbmMessageCarouselCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ], @@ -132,7 +132,7 @@ def make_instance(self, include_optional) -> RbmMessageCarouselCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_message_content_rich_card.py b/test/unit/models/test_rbm_message_content_rich_card.py index 6cdd869c..97b553c5 100644 --- a/test/unit/models/test_rbm_message_content_rich_card.py +++ b/test/unit/models/test_rbm_message_content_rich_card.py @@ -56,7 +56,7 @@ def testRbmMessageContentRichCard(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -66,7 +66,7 @@ def testRbmMessageContentRichCard(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -87,7 +87,7 @@ def testRbmMessageContentRichCard(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ], @@ -105,7 +105,7 @@ def testRbmMessageContentRichCard(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ], @@ -116,7 +116,7 @@ def testRbmMessageContentRichCard(self): RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_message_content_text.py b/test/unit/models/test_rbm_message_content_text.py index b5f33a12..53163cbe 100644 --- a/test/unit/models/test_rbm_message_content_text.py +++ b/test/unit/models/test_rbm_message_content_text.py @@ -41,7 +41,7 @@ def make_instance(self, include_optional) -> RbmMessageContentText: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_message_media.py b/test/unit/models/test_rbm_message_media.py index 0d7dcbb6..8cb485a2 100644 --- a/test/unit/models/test_rbm_message_media.py +++ b/test/unit/models/test_rbm_message_media.py @@ -45,7 +45,7 @@ def make_instance(self, include_optional) -> RbmMessageMedia: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_session.py b/test/unit/models/test_rbm_session.py new file mode 100644 index 00000000..2cd7a5eb --- /dev/null +++ b/test/unit/models/test_rbm_session.py @@ -0,0 +1,60 @@ +# coding: utf-8 + +""" + Bandwidth + + Bandwidth's Communication APIs + + The version of the OpenAPI document: 1.0.0 + Contact: letstalk@bandwidth.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +from datetime import datetime + +from bandwidth.models.rbm_session import RbmSession + +class TestRbmSession(unittest.TestCase): + """RbmSession unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> RbmSession: + """Test RbmSession + include_optional is a boolean, when False only required + params are included, when True both required and + optional params are included """ + if include_optional: + return RbmSession( + can_initiate = False, + id = '0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d', + type = 'INTERACTIVE_SESSION', + start_time = '2026-08-06T09:59:55Z', + end_time = '2026-08-07T09:59:55Z' + ) + else: + return RbmSession( + can_initiate = False, + ) + + def testRbmSession(self): + """Test RbmSession""" + instance = self.make_instance(True) + assert instance is not None + assert isinstance(instance, RbmSession) + assert instance.can_initiate == False + assert instance.id == '0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d' + assert instance.type == 'INTERACTIVE_SESSION' + assert isinstance(instance.start_time, datetime) + assert isinstance(instance.end_time, datetime) + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/models/test_rbm_standalone_card.py b/test/unit/models/test_rbm_standalone_card.py index 1e87da8e..ca59d12e 100644 --- a/test/unit/models/test_rbm_standalone_card.py +++ b/test/unit/models/test_rbm_standalone_card.py @@ -52,7 +52,7 @@ def make_instance(self, include_optional) -> RbmStandaloneCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -62,7 +62,7 @@ def make_instance(self, include_optional) -> RbmStandaloneCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] @@ -80,7 +80,7 @@ def make_instance(self, include_optional) -> RbmStandaloneCard: RbmActionBase( type = 'REPLY', text = 'Hello world', - postback_data = '[B@32298473' + postback_data = 'postbackData' ) ) ] diff --git a/test/unit/models/test_rbm_suggestion_response.py b/test/unit/models/test_rbm_suggestion_response.py index 04987ad9..1d89b773 100644 --- a/test/unit/models/test_rbm_suggestion_response.py +++ b/test/unit/models/test_rbm_suggestion_response.py @@ -34,7 +34,7 @@ def make_instance(self, include_optional) -> RbmSuggestionResponse: if include_optional: return RbmSuggestionResponse( text = 'Yes, I would like to proceed', - postback_data = '[B@5d4a34ff', + postback_data = 'postbackData', paired_message_id = '1234567890' ) else: @@ -47,7 +47,7 @@ def testRbmSuggestionResponse(self): assert instance is not None assert isinstance(instance, RbmSuggestionResponse) assert instance.text == 'Yes, I would like to proceed' - assert instance.postback_data == '[B@5d4a34ff' + assert instance.postback_data == 'postbackData' assert instance.paired_message_id == '1234567890' if __name__ == '__main__': diff --git a/test/unit/models/test_status_callback_message.py b/test/unit/models/test_status_callback_message.py index 97e879cc..11eb610c 100644 --- a/test/unit/models/test_status_callback_message.py +++ b/test/unit/models/test_status_callback_message.py @@ -16,6 +16,7 @@ import unittest from datetime import datetime +from bandwidth.models.rbm_session import RbmSession from bandwidth.models.status_callback_message import StatusCallbackMessage class TestStatusCallbackMessage(unittest.TestCase): @@ -46,7 +47,13 @@ def make_instance(self, include_optional) -> StatusCallbackMessage: tag = 'custom string', media = ["https://dev.bandwidth.com/images/bandwidth-logo.png","https://dev.bandwidth.com/images/github_logo.png"], priority = 'default', - channel = 'RBM' + channel = 'RBM', + session = RbmSession( + can_initiate = False, + id = '0f5a3b9c-2d4e-4f6a-8b1c-7e9d0a2b3c4d', + type = 'INTERACTIVE_SESSION', + start_time = '2026-08-06T09:59:55Z', + end_time = '2026-08-07T09:59:55Z', ) ) else: return StatusCallbackMessage( @@ -78,6 +85,7 @@ def testStatusCallbackMessage(self): assert instance.media == ["https://dev.bandwidth.com/images/bandwidth-logo.png", "https://dev.bandwidth.com/images/github_logo.png"] assert instance.priority == 'default' assert instance.channel == 'RBM' + assert isinstance(instance.session, RbmSession) if __name__ == '__main__': unittest.main() diff --git a/test/unit/models/test_status_callback_type_enum.py b/test/unit/models/test_status_callback_type_enum.py index fd5e9f45..2505207e 100644 --- a/test/unit/models/test_status_callback_type_enum.py +++ b/test/unit/models/test_status_callback_type_enum.py @@ -33,6 +33,7 @@ def testStatusCallbackTypeEnum(self): assert StatusCallbackTypeEnum('message-delivered') == 'message-delivered' assert StatusCallbackTypeEnum('message-failed') == 'message-failed' assert StatusCallbackTypeEnum('message-read') == 'message-read' + assert StatusCallbackTypeEnum('session-started') == 'session-started' if __name__ == '__main__': unittest.main() diff --git a/test/unit/models/test_transfer_disconnect_callback.py b/test/unit/models/test_transfer_disconnect_callback.py index cf5f4e50..8cdab874 100644 --- a/test/unit/models/test_transfer_disconnect_callback.py +++ b/test/unit/models/test_transfer_disconnect_callback.py @@ -53,7 +53,10 @@ def make_instance(self, include_optional) -> TransferDisconnectCallback: transfer_to = '+15555555555', cause = 'busy', error_message = 'Call c-2a913f94-6a486f3a-3cae-4034-bcc3-f0c9fa77ca2f is already bridged with another call', - error_id = '4642074b-7b58-478b-96e4-3a60955c6765' + error_id = '4642074b-7b58-478b-96e4-3a60955c6765', + sip_response_code = 603, + sip_status_line = 'SIP/2.0 603 Network Blocked', + sip_headers = {'reason': 'v=analytics1'} ) else: return TransferDisconnectCallback( @@ -83,6 +86,9 @@ def testTransferDisconnectCallback(self): assert instance.cause == 'busy' assert instance.error_message == 'Call c-2a913f94-6a486f3a-3cae-4034-bcc3-f0c9fa77ca2f is already bridged with another call' assert instance.error_id == '4642074b-7b58-478b-96e4-3a60955c6765' + assert instance.sip_response_code == 603 + assert instance.sip_status_line == 'SIP/2.0 603 Network Blocked' + assert instance.sip_headers == {'reason': 'v=analytics1'} if __name__ == '__main__': unittest.main()