diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 72019e396..acf7fd8f0 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.182.0" + ".": "4.183.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 35e5522ba..1f749962d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 1284 +configured_endpoints: 1296 diff --git a/CHANGELOG.md b/CHANGELOG.md index 46fed3ebd..f03178684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [4.183.0](https://github.com/team-telnyx/telnyx-python/compare/v4.182.0...v4.183.0) (2026-10-01) + + +### Features + +* promote from staging f8d5d63 ([bb4d1d3](https://github.com/team-telnyx/telnyx-python/commit/bb4d1d3c17ca329b6c41ba5ac38924223fc4c0e7)) + ## [4.182.0](https://github.com/team-telnyx/telnyx-python/compare/v4.181.0...v4.182.0) (2026-09-25) diff --git a/api.md b/api.md index 77ff31afa..99d30f25d 100644 --- a/api.md +++ b/api.md @@ -620,6 +620,7 @@ from telnyx.types.ai import ( ComparisonExpression, ConversationFlow, ConversationFlowReq, + DelegationSettings, EnabledFeatures, Expression, ExternalLlm, @@ -659,11 +660,13 @@ from telnyx.types.ai import ( TransferTool, VoiceSettings, WebhookTool, + WebsocketSettings, WidgetSettings, AssistantDeleteResponse, AssistantChatResponse, AssistantGetTexmlResponse, AssistantSendSMSResponse, + AssistantWhatsappResponse, ) ``` @@ -679,6 +682,7 @@ Methods: - client.ai.assistants.get_texml(assistant_id) -> str - client.ai.assistants.imports(\*\*params) -> AssistantsList - client.ai.assistants.send_sms(assistant_id, \*\*params) -> AssistantSendSMSResponse +- client.ai.assistants.whatsapp(assistant_id, \*\*params) -> AssistantWhatsappResponse ### Tests @@ -1361,12 +1365,20 @@ Methods: Types: ```python -from telnyx.types.ai.memory import NamespaceRetrieveResponse +from telnyx.types.ai.memory import ( + Namespace, + NamespaceCreateResponse, + NamespaceRetrieveResponse, + NamespaceListResponse, +) ``` Methods: +- client.ai.memory.namespaces.create(\*\*params) -> NamespaceCreateResponse - client.ai.memory.namespaces.retrieve(operation_id, \*, namespace) -> NamespaceRetrieveResponse +- client.ai.memory.namespaces.list() -> NamespaceListResponse +- client.ai.memory.namespaces.delete(namespace) -> None #### Profiles @@ -6163,6 +6175,19 @@ Methods: - client.enterprises.dir.create(enterprise_id, \*\*params) -> DirWrapped - client.enterprises.dir.list(enterprise_id, \*\*params) -> SyncDefaultFlatPagination[Dir] +## VerifyEmail + +Types: + +```python +from telnyx.types.enterprises import EnterpriseEmailVerificationStatusWrapped +``` + +Methods: + +- client.enterprises.verify_email.create(enterprise_id) -> EnterpriseEmailVerificationStatusWrapped +- client.enterprises.verify_email.confirm(enterprise_id, \*\*params) -> EnterpriseEmailVerificationStatusWrapped + # Reputation ## Numbers @@ -6311,7 +6336,18 @@ Methods: Types: ```python -from telnyx.types import Dir, DirList, DirStatus, DirWrapped, Document, DirListDocumentTypesResponse +from telnyx.types import ( + BpoAuthorizationInput, + Dir, + DirList, + DirStatus, + DirWrapped, + Document, + SignaturePayload, + DirDeleteResponse, + DirListDocumentTypesResponse, + DirRetrieveBpoAuthorizationsResponse, +) ``` Methods: @@ -6319,10 +6355,12 @@ Methods: - client.dir.retrieve(dir_id) -> DirWrapped - client.dir.update(dir_id, \*\*params) -> DirWrapped - client.dir.list(\*\*params) -> SyncDefaultFlatPagination[Dir] -- client.dir.delete(dir_id) -> None +- client.dir.delete(dir_id) -> DirDeleteResponse +- client.dir.bpo_loa(dir_id, \*\*params) -> BinaryAPIResponse - client.dir.list_document_types() -> DirListDocumentTypesResponse - client.dir.list_infringement_claims(dir_id, \*\*params) -> SyncDefaultFlatPagination[InfringementClaim] - client.dir.new_loa(dir_id, \*\*params) -> BinaryAPIResponse +- client.dir.retrieve_bpo_authorizations(dir_id, \*\*params) -> DirRetrieveBpoAuthorizationsResponse - client.dir.submit(dir_id) -> DirWrapped - client.dir.update_infringement(dir_id, \*\*params) -> DirWrapped @@ -7025,3 +7063,18 @@ from telnyx.types import MachinePaymentAccountCreditResponse Methods: - client.machine_payments.account_credit(\*\*params) -> MachinePaymentAccountCreditResponse + +# SpendLimits + +Types: + +```python +from telnyx.types import SpendLimit, SpendLimitPeriod, SpendLimitResponse, SpendLimitListResponse +``` + +Methods: + +- client.spend_limits.create(\*\*params) -> SpendLimitResponse +- client.spend_limits.update(product, \*\*params) -> SpendLimitResponse +- client.spend_limits.list() -> SpendLimitListResponse +- client.spend_limits.delete(product, \*\*params) -> SpendLimitResponse diff --git a/pyproject.toml b/pyproject.toml index 943ee0b1a..7221235ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "telnyx" -version = "4.182.0" +version = "4.183.0" description = "The official Python library for the telnyx API" dynamic = ["readme"] license = "MIT" diff --git a/src/telnyx/_client.py b/src/telnyx/_client.py index 8a9e789f8..c0dbe73bb 100644 --- a/src/telnyx/_client.py +++ b/src/telnyx/_client.py @@ -93,6 +93,7 @@ email_events, oauth_grants, requirements, + spend_limits, voice_clones, bot_challenge, channel_zones, @@ -259,6 +260,7 @@ from .resources.email_events import EmailEventsResource, AsyncEmailEventsResource from .resources.oauth_grants import OAuthGrantsResource, AsyncOAuthGrantsResource from .resources.requirements import RequirementsResource, AsyncRequirementsResource + from .resources.spend_limits import SpendLimitsResource, AsyncSpendLimitsResource from .resources.voice_clones import VoiceClonesResource, AsyncVoiceClonesResource from .resources.bot_challenge import BotChallengeResource, AsyncBotChallengeResource from .resources.channel_zones import ChannelZonesResource, AsyncChannelZonesResource @@ -1920,6 +1922,22 @@ def machine_payments(self) -> MachinePaymentsResource: return MachinePaymentsResource(self) + @cached_property + def spend_limits(self) -> SpendLimitsResource: + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + from .resources.spend_limits import SpendLimitsResource + + return SpendLimitsResource(self) + @cached_property def with_raw_response(self) -> TelnyxWithRawResponse: return TelnyxWithRawResponse(self) @@ -3473,6 +3491,22 @@ def machine_payments(self) -> AsyncMachinePaymentsResource: return AsyncMachinePaymentsResource(self) + @cached_property + def spend_limits(self) -> AsyncSpendLimitsResource: + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + from .resources.spend_limits import AsyncSpendLimitsResource + + return AsyncSpendLimitsResource(self) + @cached_property def with_raw_response(self) -> AsyncTelnyxWithRawResponse: return AsyncTelnyxWithRawResponse(self) @@ -4962,6 +4996,22 @@ def machine_payments(self) -> machine_payments.MachinePaymentsResourceWithRawRes return MachinePaymentsResourceWithRawResponse(self._client.machine_payments) + @cached_property + def spend_limits(self) -> spend_limits.SpendLimitsResourceWithRawResponse: + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + from .resources.spend_limits import SpendLimitsResourceWithRawResponse + + return SpendLimitsResourceWithRawResponse(self._client.spend_limits) + class AsyncTelnyxWithRawResponse: _client: AsyncTelnyx @@ -6324,6 +6374,22 @@ def machine_payments(self) -> machine_payments.AsyncMachinePaymentsResourceWithR return AsyncMachinePaymentsResourceWithRawResponse(self._client.machine_payments) + @cached_property + def spend_limits(self) -> spend_limits.AsyncSpendLimitsResourceWithRawResponse: + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + from .resources.spend_limits import AsyncSpendLimitsResourceWithRawResponse + + return AsyncSpendLimitsResourceWithRawResponse(self._client.spend_limits) + class TelnyxWithStreamedResponse: _client: Telnyx @@ -7688,6 +7754,22 @@ def machine_payments(self) -> machine_payments.MachinePaymentsResourceWithStream return MachinePaymentsResourceWithStreamingResponse(self._client.machine_payments) + @cached_property + def spend_limits(self) -> spend_limits.SpendLimitsResourceWithStreamingResponse: + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + from .resources.spend_limits import SpendLimitsResourceWithStreamingResponse + + return SpendLimitsResourceWithStreamingResponse(self._client.spend_limits) + class AsyncTelnyxWithStreamedResponse: _client: AsyncTelnyx @@ -9100,6 +9182,22 @@ def machine_payments(self) -> machine_payments.AsyncMachinePaymentsResourceWithS return AsyncMachinePaymentsResourceWithStreamingResponse(self._client.machine_payments) + @cached_property + def spend_limits(self) -> spend_limits.AsyncSpendLimitsResourceWithStreamingResponse: + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + from .resources.spend_limits import AsyncSpendLimitsResourceWithStreamingResponse + + return AsyncSpendLimitsResourceWithStreamingResponse(self._client.spend_limits) + Client = Telnyx diff --git a/src/telnyx/_version.py b/src/telnyx/_version.py index d9fdc8dc7..b27cecb7a 100644 --- a/src/telnyx/_version.py +++ b/src/telnyx/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "telnyx" -__version__ = "4.182.0" # x-release-please-version +__version__ = "4.183.0" # x-release-please-version diff --git a/src/telnyx/resources/__init__.py b/src/telnyx/resources/__init__.py index a07625f89..5c1accd65 100644 --- a/src/telnyx/resources/__init__.py +++ b/src/telnyx/resources/__init__.py @@ -449,6 +449,14 @@ RequirementsResourceWithStreamingResponse, AsyncRequirementsResourceWithStreamingResponse, ) +from .spend_limits import ( + SpendLimitsResource, + AsyncSpendLimitsResource, + SpendLimitsResourceWithRawResponse, + AsyncSpendLimitsResourceWithRawResponse, + SpendLimitsResourceWithStreamingResponse, + AsyncSpendLimitsResourceWithStreamingResponse, +) from .voice_clones import ( VoiceClonesResource, AsyncVoiceClonesResource, @@ -2623,4 +2631,10 @@ "AsyncMachinePaymentsResourceWithRawResponse", "MachinePaymentsResourceWithStreamingResponse", "AsyncMachinePaymentsResourceWithStreamingResponse", + "SpendLimitsResource", + "AsyncSpendLimitsResource", + "SpendLimitsResourceWithRawResponse", + "AsyncSpendLimitsResourceWithRawResponse", + "SpendLimitsResourceWithStreamingResponse", + "AsyncSpendLimitsResourceWithStreamingResponse", ] diff --git a/src/telnyx/resources/ai/assistants/assistants.py b/src/telnyx/resources/ai/assistants/assistants.py index 3fe718d55..eef17a16c 100644 --- a/src/telnyx/resources/ai/assistants/assistants.py +++ b/src/telnyx/resources/ai/assistants/assistants.py @@ -41,6 +41,7 @@ assistant_imports_params, assistant_retrieve_params, assistant_send_sms_params, + assistant_whatsapp_params, ) from .tests.tests import ( TestsResource, @@ -94,12 +95,15 @@ from ....types.ai.observability_req_param import ObservabilityReqParam from ....types.ai.messaging_settings_param import MessagingSettingsParam from ....types.ai.telephony_settings_param import TelephonySettingsParam +from ....types.ai.websocket_settings_param import WebsocketSettingsParam from ....types.ai.assistant_delete_response import AssistantDeleteResponse +from ....types.ai.delegation_settings_param import DelegationSettingsParam from ....types.ai.fallback_config_req_param import FallbackConfigReqParam from ....types.ai.assistant_a2_a_agent_param import AssistantA2AAgentParam from ....types.ai.assistant_mcp_server_param import AssistantMcpServerParam from ....types.ai.assistant_integration_param import AssistantIntegrationParam from ....types.ai.assistant_send_sms_response import AssistantSendSMSResponse +from ....types.ai.assistant_whatsapp_response import AssistantWhatsappResponse from ....types.ai.conversation_flow_req_param import ConversationFlowReqParam from ....types.ai.transcription_settings_param import TranscriptionSettingsParam from ....types.ai.post_conversation_settings_req_param import PostConversationSettingsReqParam @@ -173,6 +177,7 @@ def create( name: str, a2a_agents: Iterable[AssistantA2AAgentParam] | Omit = omit, conversation_flow: ConversationFlowReqParam | Omit = omit, + delegation_settings: DelegationSettingsParam | Omit = omit, description: str | Omit = omit, dynamic_variables: Dict[str, object] | Omit = omit, dynamic_variables_webhook_timeout_ms: int | Omit = omit, @@ -197,6 +202,7 @@ def create( tools: Iterable[AssistantToolParam] | Omit = omit, transcription: TranscriptionSettingsParam | Omit = omit, voice_settings: InferenceEmbeddingVoiceSettingsParam | Omit = omit, + websocket_settings: WebsocketSettingsParam | Omit = omit, widget_settings: WidgetSettingsParam | Omit = omit, idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -229,6 +235,14 @@ def create( unique node/edge IDs, that `start_node_id` references a real node, and that every edge's endpoints reference real nodes. + delegation_settings: Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + dynamic_variables: Map of dynamic variables and their default values dynamic_variables_webhook_timeout_ms: Timeout in milliseconds for the dynamic variables webhook. Must be between 1 and @@ -297,6 +311,12 @@ def create( assistant. Prefer `tool_ids` to attach shared tools created with the AI Tools endpoints. + websocket_settings: Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + widget_settings: Configuration settings for the assistant's web widget. extra_headers: Send extra headers @@ -316,6 +336,7 @@ def create( "name": name, "a2a_agents": a2a_agents, "conversation_flow": conversation_flow, + "delegation_settings": delegation_settings, "description": description, "dynamic_variables": dynamic_variables, "dynamic_variables_webhook_timeout_ms": dynamic_variables_webhook_timeout_ms, @@ -340,6 +361,7 @@ def create( "tools": tools, "transcription": transcription, "voice_settings": voice_settings, + "websocket_settings": websocket_settings, "widget_settings": widget_settings, }, assistant_create_params.AssistantCreateParams, @@ -413,6 +435,7 @@ def update( *, a2a_agents: Iterable[AssistantA2AAgentParam] | Omit = omit, conversation_flow: ConversationFlowReqParam | Omit = omit, + delegation_settings: DelegationSettingsParam | Omit = omit, description: str | Omit = omit, dynamic_variables: Dict[str, object] | Omit = omit, dynamic_variables_webhook_timeout_ms: int | Omit = omit, @@ -441,6 +464,7 @@ def update( transcription: TranscriptionSettingsParam | Omit = omit, version_name: str | Omit = omit, voice_settings: InferenceEmbeddingVoiceSettingsParam | Omit = omit, + websocket_settings: WebsocketSettingsParam | Omit = omit, widget_settings: WidgetSettingsParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -471,6 +495,14 @@ def update( unique node/edge IDs, that `start_node_id` references a real node, and that every edge's endpoints reference real nodes. + delegation_settings: Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + dynamic_variables: Map of dynamic variables and their default values dynamic_variables_webhook_timeout_ms: Timeout in milliseconds for the dynamic variables webhook. Must be between 1 and @@ -558,6 +590,12 @@ def update( version_name: Human-readable name for the assistant version. + websocket_settings: Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + widget_settings: Configuration settings for the assistant's web widget. extra_headers: Send extra headers @@ -576,6 +614,7 @@ def update( { "a2a_agents": a2a_agents, "conversation_flow": conversation_flow, + "delegation_settings": delegation_settings, "description": description, "dynamic_variables": dynamic_variables, "dynamic_variables_webhook_timeout_ms": dynamic_variables_webhook_timeout_ms, @@ -604,6 +643,7 @@ def update( "transcription": transcription, "version_name": version_name, "voice_settings": voice_settings, + "websocket_settings": websocket_settings, "widget_settings": widget_settings, }, assistant_update_params.AssistantUpdateParams, @@ -916,6 +956,81 @@ def send_sms( cast_to=AssistantSendSMSResponse, ) + def whatsapp( + self, + assistant_id: str, + *, + content: str, + from_: str, + to: str, + conversation_metadata: Dict[str, Union[str, int, bool]] | Omit = omit, + idempotency_key: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AssistantWhatsappResponse: + """Start a WhatsApp conversation with a customer from the business side. + + This + endpoint: + + 1. Validates that `from` is a WhatsApp number on your account whose messaging + profile has this assistant configured + 2. Creates a new `whatsapp_chat` conversation with the provided metadata + 3. Asks the assistant to pick one of its approved WhatsApp templates and fill + its variables from `content` + 4. Sends the template from `from` to `to` + 5. Returns the conversation ID and the message ID + + When the customer replies, the reply is routed to the same conversation and the + assistant answers within the 24-hour customer service window. The assistant + needs a `whatsapp_template` tool with at least one approved template, data + retention enabled and PII redaction disabled. + + Args: + content: Instruction for the assistant, including the values for the template variables, + e.g. `Send the login verification code 482913 to the customer.` + + from_: WhatsApp number on your account to send from, in E.164 format. Its messaging + profile must have this assistant configured. + + to: Customer to message, as an E.164 phone number or a WhatsApp business-scoped user + ID (BSUID). + + conversation_metadata: Metadata stored on the conversation. Keys starting with `telnyx_` and the + `assistant_id` key are reserved. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not assistant_id: + raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") + extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} + return self._post( + path_template("/ai/assistants/{assistant_id}/chat/whatsapp", assistant_id=assistant_id), + body=maybe_transform( + { + "content": content, + "from_": from_, + "to": to, + "conversation_metadata": conversation_metadata, + }, + assistant_whatsapp_params.AssistantWhatsappParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AssistantWhatsappResponse, + ) + class AsyncAssistantsResource(AsyncAPIResource): """Configure AI assistant specifications""" @@ -981,6 +1096,7 @@ async def create( name: str, a2a_agents: Iterable[AssistantA2AAgentParam] | Omit = omit, conversation_flow: ConversationFlowReqParam | Omit = omit, + delegation_settings: DelegationSettingsParam | Omit = omit, description: str | Omit = omit, dynamic_variables: Dict[str, object] | Omit = omit, dynamic_variables_webhook_timeout_ms: int | Omit = omit, @@ -1005,6 +1121,7 @@ async def create( tools: Iterable[AssistantToolParam] | Omit = omit, transcription: TranscriptionSettingsParam | Omit = omit, voice_settings: InferenceEmbeddingVoiceSettingsParam | Omit = omit, + websocket_settings: WebsocketSettingsParam | Omit = omit, widget_settings: WidgetSettingsParam | Omit = omit, idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1037,6 +1154,14 @@ async def create( unique node/edge IDs, that `start_node_id` references a real node, and that every edge's endpoints reference real nodes. + delegation_settings: Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + dynamic_variables: Map of dynamic variables and their default values dynamic_variables_webhook_timeout_ms: Timeout in milliseconds for the dynamic variables webhook. Must be between 1 and @@ -1105,6 +1230,12 @@ async def create( assistant. Prefer `tool_ids` to attach shared tools created with the AI Tools endpoints. + websocket_settings: Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + widget_settings: Configuration settings for the assistant's web widget. extra_headers: Send extra headers @@ -1124,6 +1255,7 @@ async def create( "name": name, "a2a_agents": a2a_agents, "conversation_flow": conversation_flow, + "delegation_settings": delegation_settings, "description": description, "dynamic_variables": dynamic_variables, "dynamic_variables_webhook_timeout_ms": dynamic_variables_webhook_timeout_ms, @@ -1148,6 +1280,7 @@ async def create( "tools": tools, "transcription": transcription, "voice_settings": voice_settings, + "websocket_settings": websocket_settings, "widget_settings": widget_settings, }, assistant_create_params.AssistantCreateParams, @@ -1221,6 +1354,7 @@ async def update( *, a2a_agents: Iterable[AssistantA2AAgentParam] | Omit = omit, conversation_flow: ConversationFlowReqParam | Omit = omit, + delegation_settings: DelegationSettingsParam | Omit = omit, description: str | Omit = omit, dynamic_variables: Dict[str, object] | Omit = omit, dynamic_variables_webhook_timeout_ms: int | Omit = omit, @@ -1249,6 +1383,7 @@ async def update( transcription: TranscriptionSettingsParam | Omit = omit, version_name: str | Omit = omit, voice_settings: InferenceEmbeddingVoiceSettingsParam | Omit = omit, + websocket_settings: WebsocketSettingsParam | Omit = omit, widget_settings: WidgetSettingsParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1279,6 +1414,14 @@ async def update( unique node/edge IDs, that `start_node_id` references a real node, and that every edge's endpoints reference real nodes. + delegation_settings: Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + dynamic_variables: Map of dynamic variables and their default values dynamic_variables_webhook_timeout_ms: Timeout in milliseconds for the dynamic variables webhook. Must be between 1 and @@ -1366,6 +1509,12 @@ async def update( version_name: Human-readable name for the assistant version. + websocket_settings: Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + widget_settings: Configuration settings for the assistant's web widget. extra_headers: Send extra headers @@ -1384,6 +1533,7 @@ async def update( { "a2a_agents": a2a_agents, "conversation_flow": conversation_flow, + "delegation_settings": delegation_settings, "description": description, "dynamic_variables": dynamic_variables, "dynamic_variables_webhook_timeout_ms": dynamic_variables_webhook_timeout_ms, @@ -1412,6 +1562,7 @@ async def update( "transcription": transcription, "version_name": version_name, "voice_settings": voice_settings, + "websocket_settings": websocket_settings, "widget_settings": widget_settings, }, assistant_update_params.AssistantUpdateParams, @@ -1724,6 +1875,81 @@ async def send_sms( cast_to=AssistantSendSMSResponse, ) + async def whatsapp( + self, + assistant_id: str, + *, + content: str, + from_: str, + to: str, + conversation_metadata: Dict[str, Union[str, int, bool]] | Omit = omit, + idempotency_key: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AssistantWhatsappResponse: + """Start a WhatsApp conversation with a customer from the business side. + + This + endpoint: + + 1. Validates that `from` is a WhatsApp number on your account whose messaging + profile has this assistant configured + 2. Creates a new `whatsapp_chat` conversation with the provided metadata + 3. Asks the assistant to pick one of its approved WhatsApp templates and fill + its variables from `content` + 4. Sends the template from `from` to `to` + 5. Returns the conversation ID and the message ID + + When the customer replies, the reply is routed to the same conversation and the + assistant answers within the 24-hour customer service window. The assistant + needs a `whatsapp_template` tool with at least one approved template, data + retention enabled and PII redaction disabled. + + Args: + content: Instruction for the assistant, including the values for the template variables, + e.g. `Send the login verification code 482913 to the customer.` + + from_: WhatsApp number on your account to send from, in E.164 format. Its messaging + profile must have this assistant configured. + + to: Customer to message, as an E.164 phone number or a WhatsApp business-scoped user + ID (BSUID). + + conversation_metadata: Metadata stored on the conversation. Keys starting with `telnyx_` and the + `assistant_id` key are reserved. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not assistant_id: + raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") + extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} + return await self._post( + path_template("/ai/assistants/{assistant_id}/chat/whatsapp", assistant_id=assistant_id), + body=await async_maybe_transform( + { + "content": content, + "from_": from_, + "to": to, + "conversation_metadata": conversation_metadata, + }, + assistant_whatsapp_params.AssistantWhatsappParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AssistantWhatsappResponse, + ) + class AssistantsResourceWithRawResponse: def __init__(self, assistants: AssistantsResource) -> None: @@ -1759,6 +1985,9 @@ def __init__(self, assistants: AssistantsResource) -> None: self.send_sms = to_raw_response_wrapper( assistants.send_sms, ) + self.whatsapp = to_raw_response_wrapper( + assistants.whatsapp, + ) @cached_property def tests(self) -> TestsResourceWithRawResponse: @@ -1830,6 +2059,9 @@ def __init__(self, assistants: AsyncAssistantsResource) -> None: self.send_sms = async_to_raw_response_wrapper( assistants.send_sms, ) + self.whatsapp = async_to_raw_response_wrapper( + assistants.whatsapp, + ) @cached_property def tests(self) -> AsyncTestsResourceWithRawResponse: @@ -1901,6 +2133,9 @@ def __init__(self, assistants: AssistantsResource) -> None: self.send_sms = to_streamed_response_wrapper( assistants.send_sms, ) + self.whatsapp = to_streamed_response_wrapper( + assistants.whatsapp, + ) @cached_property def tests(self) -> TestsResourceWithStreamingResponse: @@ -1972,6 +2207,9 @@ def __init__(self, assistants: AsyncAssistantsResource) -> None: self.send_sms = async_to_streamed_response_wrapper( assistants.send_sms, ) + self.whatsapp = async_to_streamed_response_wrapper( + assistants.whatsapp, + ) @cached_property def tests(self) -> AsyncTestsResourceWithStreamingResponse: diff --git a/src/telnyx/resources/ai/assistants/versions.py b/src/telnyx/resources/ai/assistants/versions.py index cbe091a4e..423eed487 100644 --- a/src/telnyx/resources/ai/assistants/versions.py +++ b/src/telnyx/resources/ai/assistants/versions.py @@ -29,6 +29,8 @@ from ....types.ai.observability_req_param import ObservabilityReqParam from ....types.ai.messaging_settings_param import MessagingSettingsParam from ....types.ai.telephony_settings_param import TelephonySettingsParam +from ....types.ai.websocket_settings_param import WebsocketSettingsParam +from ....types.ai.delegation_settings_param import DelegationSettingsParam from ....types.ai.fallback_config_req_param import FallbackConfigReqParam from ....types.ai.assistant_a2_a_agent_param import AssistantA2AAgentParam from ....types.ai.assistant_mcp_server_param import AssistantMcpServerParam @@ -118,6 +120,7 @@ def update( assistant_id: str, a2a_agents: Iterable[AssistantA2AAgentParam] | Omit = omit, conversation_flow: ConversationFlowReqParam | Omit = omit, + delegation_settings: DelegationSettingsParam | Omit = omit, description: str | Omit = omit, dynamic_variables: Dict[str, object] | Omit = omit, dynamic_variables_webhook_timeout_ms: int | Omit = omit, @@ -145,6 +148,7 @@ def update( transcription: TranscriptionSettingsParam | Omit = omit, version_name: str | Omit = omit, voice_settings: InferenceEmbeddingVoiceSettingsParam | Omit = omit, + websocket_settings: WebsocketSettingsParam | Omit = omit, widget_settings: WidgetSettingsParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -175,6 +179,14 @@ def update( unique node/edge IDs, that `start_node_id` references a real node, and that every edge's endpoints reference real nodes. + delegation_settings: Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + dynamic_variables: Map of dynamic variables and their default values dynamic_variables_webhook_timeout_ms: Timeout in milliseconds for the dynamic variables webhook. Must be between 1 and @@ -259,6 +271,12 @@ def update( version_name: Human-readable name for the assistant version. + websocket_settings: Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + widget_settings: Configuration settings for the assistant's web widget. extra_headers: Send extra headers @@ -281,6 +299,7 @@ def update( { "a2a_agents": a2a_agents, "conversation_flow": conversation_flow, + "delegation_settings": delegation_settings, "description": description, "dynamic_variables": dynamic_variables, "dynamic_variables_webhook_timeout_ms": dynamic_variables_webhook_timeout_ms, @@ -308,6 +327,7 @@ def update( "transcription": transcription, "version_name": version_name, "voice_settings": voice_settings, + "websocket_settings": websocket_settings, "widget_settings": widget_settings, }, version_update_params.VersionUpdateParams, @@ -512,6 +532,7 @@ async def update( assistant_id: str, a2a_agents: Iterable[AssistantA2AAgentParam] | Omit = omit, conversation_flow: ConversationFlowReqParam | Omit = omit, + delegation_settings: DelegationSettingsParam | Omit = omit, description: str | Omit = omit, dynamic_variables: Dict[str, object] | Omit = omit, dynamic_variables_webhook_timeout_ms: int | Omit = omit, @@ -539,6 +560,7 @@ async def update( transcription: TranscriptionSettingsParam | Omit = omit, version_name: str | Omit = omit, voice_settings: InferenceEmbeddingVoiceSettingsParam | Omit = omit, + websocket_settings: WebsocketSettingsParam | Omit = omit, widget_settings: WidgetSettingsParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -569,6 +591,14 @@ async def update( unique node/edge IDs, that `start_node_id` references a real node, and that every edge's endpoints reference real nodes. + delegation_settings: Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + dynamic_variables: Map of dynamic variables and their default values dynamic_variables_webhook_timeout_ms: Timeout in milliseconds for the dynamic variables webhook. Must be between 1 and @@ -653,6 +683,12 @@ async def update( version_name: Human-readable name for the assistant version. + websocket_settings: Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + widget_settings: Configuration settings for the assistant's web widget. extra_headers: Send extra headers @@ -675,6 +711,7 @@ async def update( { "a2a_agents": a2a_agents, "conversation_flow": conversation_flow, + "delegation_settings": delegation_settings, "description": description, "dynamic_variables": dynamic_variables, "dynamic_variables_webhook_timeout_ms": dynamic_variables_webhook_timeout_ms, @@ -702,6 +739,7 @@ async def update( "transcription": transcription, "version_name": version_name, "voice_settings": voice_settings, + "websocket_settings": websocket_settings, "widget_settings": widget_settings, }, version_update_params.VersionUpdateParams, diff --git a/src/telnyx/resources/ai/memory/memory.py b/src/telnyx/resources/ai/memory/memory.py index 92bcab36b..36eddf88d 100644 --- a/src/telnyx/resources/ai/memory/memory.py +++ b/src/telnyx/resources/ai/memory/memory.py @@ -19,7 +19,6 @@ class MemoryResource(SyncAPIResource): @cached_property def namespaces(self) -> NamespacesResource: - """Whether a write has finished.""" return NamespacesResource(self._client) @cached_property @@ -45,7 +44,6 @@ def with_streaming_response(self) -> MemoryResourceWithStreamingResponse: class AsyncMemoryResource(AsyncAPIResource): @cached_property def namespaces(self) -> AsyncNamespacesResource: - """Whether a write has finished.""" return AsyncNamespacesResource(self._client) @cached_property @@ -74,7 +72,6 @@ def __init__(self, memory: MemoryResource) -> None: @cached_property def namespaces(self) -> NamespacesResourceWithRawResponse: - """Whether a write has finished.""" return NamespacesResourceWithRawResponse(self._memory.namespaces) @@ -84,7 +81,6 @@ def __init__(self, memory: AsyncMemoryResource) -> None: @cached_property def namespaces(self) -> AsyncNamespacesResourceWithRawResponse: - """Whether a write has finished.""" return AsyncNamespacesResourceWithRawResponse(self._memory.namespaces) @@ -94,7 +90,6 @@ def __init__(self, memory: MemoryResource) -> None: @cached_property def namespaces(self) -> NamespacesResourceWithStreamingResponse: - """Whether a write has finished.""" return NamespacesResourceWithStreamingResponse(self._memory.namespaces) @@ -104,5 +99,4 @@ def __init__(self, memory: AsyncMemoryResource) -> None: @cached_property def namespaces(self) -> AsyncNamespacesResourceWithStreamingResponse: - """Whether a write has finished.""" return AsyncNamespacesResourceWithStreamingResponse(self._memory.namespaces) diff --git a/src/telnyx/resources/ai/memory/namespaces/namespaces.py b/src/telnyx/resources/ai/memory/namespaces/namespaces.py index ad927a82e..f3409462d 100644 --- a/src/telnyx/resources/ai/memory/namespaces/namespaces.py +++ b/src/telnyx/resources/ai/memory/namespaces/namespaces.py @@ -12,8 +12,8 @@ SettingsResourceWithStreamingResponse, AsyncSettingsResourceWithStreamingResponse, ) -from ....._types import Body, Query, Headers, NotGiven, not_given -from ....._utils import path_template +from ....._types import Body, Query, Headers, NoneType, NotGiven, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform from ....._compat import cached_property from ....._resource import SyncAPIResource, AsyncAPIResource from ....._response import ( @@ -31,14 +31,15 @@ ProfilesResourceWithStreamingResponse, AsyncProfilesResourceWithStreamingResponse, ) +from .....types.ai.memory import namespace_create_params +from .....types.ai.memory.namespace_list_response import NamespaceListResponse +from .....types.ai.memory.namespace_create_response import NamespaceCreateResponse from .....types.ai.memory.namespace_retrieve_response import NamespaceRetrieveResponse __all__ = ["NamespacesResource", "AsyncNamespacesResource"] class NamespacesResource(SyncAPIResource): - """Whether a write has finished.""" - @cached_property def profiles(self) -> ProfilesResource: return ProfilesResource(self._client) @@ -67,6 +68,42 @@ def with_streaming_response(self) -> NamespacesResourceWithStreamingResponse: """ return NamespacesResourceWithStreamingResponse(self) + def create( + self, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NamespaceCreateResponse: + """Create a namespace. + + An organization can have at most five, `default` among them + — a sixth returns `403`. + + Args: + name: A name for the new namespace, unique within your organization. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ai/memory/namespaces", + body=maybe_transform({"name": name}, namespace_create_params.NamespaceCreateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NamespaceCreateResponse, + ) + def retrieve( self, operation_id: str, @@ -110,10 +147,65 @@ def retrieve( cast_to=NamespaceRetrieveResponse, ) + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NamespaceListResponse: + """Every namespace in your organization, `default` among them.""" + return self._get( + "/ai/memory/namespaces", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NamespaceListResponse, + ) + + def delete( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Delete a namespace and every profile and memory in it. -class AsyncNamespacesResource(AsyncAPIResource): - """Whether a write has finished.""" + `default` cannot be + deleted. This cannot be undone. + + Args: + namespace: The namespace to delete. `default` cannot be deleted. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/ai/memory/namespaces/{namespace}", namespace=namespace), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncNamespacesResource(AsyncAPIResource): @cached_property def profiles(self) -> AsyncProfilesResource: return AsyncProfilesResource(self._client) @@ -142,6 +234,42 @@ def with_streaming_response(self) -> AsyncNamespacesResourceWithStreamingRespons """ return AsyncNamespacesResourceWithStreamingResponse(self) + async def create( + self, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NamespaceCreateResponse: + """Create a namespace. + + An organization can have at most five, `default` among them + — a sixth returns `403`. + + Args: + name: A name for the new namespace, unique within your organization. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ai/memory/namespaces", + body=await async_maybe_transform({"name": name}, namespace_create_params.NamespaceCreateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NamespaceCreateResponse, + ) + async def retrieve( self, operation_id: str, @@ -185,14 +313,80 @@ async def retrieve( cast_to=NamespaceRetrieveResponse, ) + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NamespaceListResponse: + """Every namespace in your organization, `default` among them.""" + return await self._get( + "/ai/memory/namespaces", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NamespaceListResponse, + ) + + async def delete( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Delete a namespace and every profile and memory in it. + + `default` cannot be + deleted. This cannot be undone. + + Args: + namespace: The namespace to delete. `default` cannot be deleted. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/ai/memory/namespaces/{namespace}", namespace=namespace), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class NamespacesResourceWithRawResponse: def __init__(self, namespaces: NamespacesResource) -> None: self._namespaces = namespaces + self.create = to_raw_response_wrapper( + namespaces.create, + ) self.retrieve = to_raw_response_wrapper( namespaces.retrieve, ) + self.list = to_raw_response_wrapper( + namespaces.list, + ) + self.delete = to_raw_response_wrapper( + namespaces.delete, + ) @cached_property def profiles(self) -> ProfilesResourceWithRawResponse: @@ -208,9 +402,18 @@ class AsyncNamespacesResourceWithRawResponse: def __init__(self, namespaces: AsyncNamespacesResource) -> None: self._namespaces = namespaces + self.create = async_to_raw_response_wrapper( + namespaces.create, + ) self.retrieve = async_to_raw_response_wrapper( namespaces.retrieve, ) + self.list = async_to_raw_response_wrapper( + namespaces.list, + ) + self.delete = async_to_raw_response_wrapper( + namespaces.delete, + ) @cached_property def profiles(self) -> AsyncProfilesResourceWithRawResponse: @@ -226,9 +429,18 @@ class NamespacesResourceWithStreamingResponse: def __init__(self, namespaces: NamespacesResource) -> None: self._namespaces = namespaces + self.create = to_streamed_response_wrapper( + namespaces.create, + ) self.retrieve = to_streamed_response_wrapper( namespaces.retrieve, ) + self.list = to_streamed_response_wrapper( + namespaces.list, + ) + self.delete = to_streamed_response_wrapper( + namespaces.delete, + ) @cached_property def profiles(self) -> ProfilesResourceWithStreamingResponse: @@ -244,9 +456,18 @@ class AsyncNamespacesResourceWithStreamingResponse: def __init__(self, namespaces: AsyncNamespacesResource) -> None: self._namespaces = namespaces + self.create = async_to_streamed_response_wrapper( + namespaces.create, + ) self.retrieve = async_to_streamed_response_wrapper( namespaces.retrieve, ) + self.list = async_to_streamed_response_wrapper( + namespaces.list, + ) + self.delete = async_to_streamed_response_wrapper( + namespaces.delete, + ) @cached_property def profiles(self) -> AsyncProfilesResourceWithStreamingResponse: diff --git a/src/telnyx/resources/ai/openai/chat.py b/src/telnyx/resources/ai/openai/chat.py index 5de789c19..8d1467650 100644 --- a/src/telnyx/resources/ai/openai/chat.py +++ b/src/telnyx/resources/ai/openai/chat.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Union, Iterable +from typing import Union, Iterable, Optional from typing_extensions import Literal import httpx @@ -55,7 +55,7 @@ def create_completion( frequency_penalty: float | Omit = omit, length_penalty: float | Omit = omit, logprobs: bool | Omit = omit, - max_tokens: int | Omit = omit, + max_tokens: Optional[int] | Omit = omit, min_p: float | Omit = omit, mode: Literal["preferred", "strict"] | Omit = omit, model: str | Omit = omit, @@ -115,7 +115,12 @@ def create_completion( returns the log probabilities of each output token returned in the `content` of `message`. - max_tokens: Maximum number of completion tokens the model should generate. + max_tokens: Maximum number of completion (output) tokens the model may generate per request. + Defaults to 8192 when omitted or `null`. Set a higher value to allow longer + completions. The model's `max_completion_tokens` metadata (see + `GET /ai/models`), when set, caps both the default and any larger explicit + value. Reasoning models consume this budget across reasoning and answer tokens + combined. min_p: This is an alternative to `top_p` that [many prefer](https://github.com/huggingface/transformers/issues/27670). Must be @@ -264,7 +269,7 @@ async def create_completion( frequency_penalty: float | Omit = omit, length_penalty: float | Omit = omit, logprobs: bool | Omit = omit, - max_tokens: int | Omit = omit, + max_tokens: Optional[int] | Omit = omit, min_p: float | Omit = omit, mode: Literal["preferred", "strict"] | Omit = omit, model: str | Omit = omit, @@ -324,7 +329,12 @@ async def create_completion( returns the log probabilities of each output token returned in the `content` of `message`. - max_tokens: Maximum number of completion tokens the model should generate. + max_tokens: Maximum number of completion (output) tokens the model may generate per request. + Defaults to 8192 when omitted or `null`. Set a higher value to allow longer + completions. The model's `max_completion_tokens` metadata (see + `GET /ai/models`), when set, caps both the default and any larger explicit + value. Reasoning models consume this budget across reasoning and answer tokens + combined. min_p: This is an alternative to `top_p` that [many prefer](https://github.com/huggingface/transformers/issues/27670). Must be diff --git a/src/telnyx/resources/calls/actions.py b/src/telnyx/resources/calls/actions.py index 96b99fdfc..42746a450 100644 --- a/src/telnyx/resources/calls/actions.py +++ b/src/telnyx/resources/calls/actions.py @@ -1736,7 +1736,7 @@ def reject( self, call_control_id: str, *, - cause: Literal["CALL_REJECTED", "USER_BUSY"], + cause: Literal["CALL_REJECTED", "NOT_FOUND", "TEMPORARILY_UNAVAILABLE", "USER_BUSY"], client_state: str | Omit = omit, command_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1754,7 +1754,11 @@ def reject( - `call.hangup` Args: - cause: Cause for call rejection. + cause: + Cause for call rejection. The cause sets the SIP response the caller receives: + `USER_BUSY` sends 486 User Busy, `CALL_REJECTED` sends 603 Decline, `NOT_FOUND` + sends 404 Not Found, and `TEMPORARILY_UNAVAILABLE` sends 480 Temporarily + Unavailable. client_state: Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string. @@ -5883,7 +5887,7 @@ async def reject( self, call_control_id: str, *, - cause: Literal["CALL_REJECTED", "USER_BUSY"], + cause: Literal["CALL_REJECTED", "NOT_FOUND", "TEMPORARILY_UNAVAILABLE", "USER_BUSY"], client_state: str | Omit = omit, command_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -5901,7 +5905,11 @@ async def reject( - `call.hangup` Args: - cause: Cause for call rejection. + cause: + Cause for call rejection. The cause sets the SIP response the caller receives: + `USER_BUSY` sends 486 User Busy, `CALL_REJECTED` sends 603 Decline, `NOT_FOUND` + sends 404 Not Found, and `TEMPORARILY_UNAVAILABLE` sends 480 Temporarily + Unavailable. client_state: Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string. diff --git a/src/telnyx/resources/dir/dir.py b/src/telnyx/resources/dir/dir.py index b2201c82a..01457f176 100644 --- a/src/telnyx/resources/dir/dir.py +++ b/src/telnyx/resources/dir/dir.py @@ -12,11 +12,13 @@ DirStatus, dir_list_params, dir_update_params, + dir_bpo_loa_params, dir_new_loa_params, dir_update_infringement_params, dir_list_infringement_claims_params, + dir_retrieve_bpo_authorizations_params, ) -from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from ..._utils import path_template, maybe_transform, async_maybe_transform from .comments import ( CommentsResource, @@ -81,7 +83,11 @@ ) from ...types.document_param import DocumentParam from ...types.infringement_claim import InfringementClaim +from ...types.dir_delete_response import DirDeleteResponse +from ...types.signature_payload_param import SignaturePayloadParam +from ...types.bpo_authorization_input_param import BpoAuthorizationInputParam from ...types.dir_list_document_types_response import DirListDocumentTypesResponse +from ...types.dir_retrieve_bpo_authorizations_response import DirRetrieveBpoAuthorizationsResponse from ...types.enterprises.reputation.agent_input_param import AgentInputParam __all__ = ["DirResource", "AsyncDirResource"] @@ -185,6 +191,7 @@ def update( *, authorizer_email: str | Omit = omit, authorizer_name: str | Omit = omit, + bpo_authorizations: Iterable[BpoAuthorizationInputParam] | Omit = omit, call_reasons: SequenceNotStr[str] | Omit = omit, certify_brand_is_accurate: bool | Omit = omit, certify_ip_ownership: bool | Omit = omit, @@ -193,6 +200,7 @@ def update( documents: Iterable[DocumentParam] | Omit = omit, logo_url: str | Omit = omit, reselling: bool | Omit = omit, + webhook_url: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -205,11 +213,14 @@ def update( DIRs in `draft`, `rejected`, `unsuccessful`, or `suspended` can be edited freely: PATCH is a pure edit, `status` is never changed, and you re-vet by calling `POST /v2/dir/{dir_id}/submit` explicitly. A `verified` DIR can also - be edited in place: a PATCH that changes any value returns the DIR to `draft` - and branded delivery stops until you re-submit and the DIR is approved again, - while a PATCH that changes nothing (an empty body or values identical to the - current ones) leaves the DIR `verified`, so idempotent retries are safe. DIRs in - any other status (`submitted`, `in_review`, `expired`, `infringement_claimed`, + be edited in place: a PATCH that changes any value returns the DIR to `draft`; + the currently approved identity keeps displaying, and the edited content goes + live only after you re-submit and the DIR is approved again. A PATCH that + changes nothing (an empty body or values identical to the current ones) leaves + the DIR `verified`, so idempotent retries are safe. Changing only + `bpo_authorizations` or `webhook_url` is the exception: the DIR stays + `verified`. Each BPO authorization is reviewed on its own instead. DIRs in any + other status (`submitted`, `in_review`, `expired`, `infringement_claimed`, `permanently_rejected`) cannot be edited. Args: @@ -219,6 +230,13 @@ def update( authorizer_name: Name of the person at your enterprise authorizing this DIR. Must be a real individual. + bpo_authorizations: Optional. Replace this DIR's authorized BPO (Business Process Outsourcer) + accounts with these, each with its signed Letter of Authorization. The supplied + list replaces the current one: a BPO left out has its authorization removed, and + a new BPO (or a changed Letter of Authorization) is created `pending` admin + review. Send an empty list to clear all authorizations; omit the field to leave + them unchanged. Editing this list does not re-vet the DIR. Maximum 10. + call_reasons: 1–10 reasons your business calls customers. Validate phrasing against `POST /call_reasons/validate`. @@ -243,6 +261,10 @@ def update( reselling: Set to true if your organization places calls on behalf of other enterprises (BPO/reseller). Updating this triggers re-vetting on next submit. + webhook_url: Optional `https://` URL that receives webhook notifications when this DIR's + compliance review completes. Send `null` to clear. Changing only this field on a + `verified` DIR does not re-vet it. Maximum 2048 characters. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -259,6 +281,7 @@ def update( { "authorizer_email": authorizer_email, "authorizer_name": authorizer_name, + "bpo_authorizations": bpo_authorizations, "call_reasons": call_reasons, "certify_brand_is_accurate": certify_brand_is_accurate, "certify_ip_ownership": certify_ip_ownership, @@ -267,6 +290,7 @@ def update( "documents": documents, "logo_url": logo_url, "reselling": reselling, + "webhook_url": webhook_url, }, dir_update_params.DirUpdateParams, ), @@ -380,12 +404,16 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """Delete a DIR. + ) -> DirDeleteResponse: + """Request deletion of a DIR. - Failure modes: `400` if a child phone number is in a non-deletable - status, `409` if the DIR has an unresolved infringement claim, `404` if the DIR - is not yours. + This does not remove the DIR on this call: it records + the request, moves the DIR to `delete_requested`, and Telnyx completes the + removal (de-registration and cleanup) shortly after. A verified DIR keeps + serving its branded identity, and keeps billing, until the removal is executed. + Failure modes: `400` if a child phone number is still attached or the DIR is + `in_review` (wait for the review to finish), `409` if the DIR has an unresolved + infringement claim, `404` if the DIR is not yours. Args: extra_headers: Send extra headers @@ -398,13 +426,73 @@ def delete( """ if not dir_id: raise ValueError(f"Expected a non-empty value for `dir_id` but received {dir_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( path_template("/dir/{dir_id}", dir_id=dir_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=DirDeleteResponse, + ) + + def bpo_loa( + self, + dir_id: str, + *, + bpo_enterprise_id: str, + signature: SignaturePayloadParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BinaryAPIResponse: + """ + The Letter of Authorization in which a Brand Owner authorizes an approved BPO + (Business Process Outsourcer) to place branded calls that display this DIR on + the owner's behalf. Both parties are read from the caller's account: the Brand + Owner is the enterprise that owns the DIR, and the BPO is `bpo_enterprise_id`. + No business identity is accepted in the body. + + When `signature` is omitted the PDF is returned unsigned so the Brand Owner can + sign it externally and the BPO can upload it via the Documents API. When + `signature` is present the PDF embeds the supplied image, printed name, and + signed-at date. + + Returns `application/pdf`. + + Args: + bpo_enterprise_id: The approved BPO enterprise the Brand Owner is authorizing. Must be a BPO + account on the caller's organization that has already been approved. + + signature: Optional. When provided the rendered PDF embeds the signature image, printed + name, and signed-at date. When absent the PDF is returned unsigned so the Brand + Owner can sign externally and the BPO can upload it via the Documents API. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dir_id: + raise ValueError(f"Expected a non-empty value for `dir_id` but received {dir_id!r}") + extra_headers = {"Accept": "application/pdf", **(extra_headers or {})} + return self._post( + path_template("/dir/{dir_id}/bpo_loa", dir_id=dir_id), + body=maybe_transform( + { + "bpo_enterprise_id": bpo_enterprise_id, + "signature": signature, + }, + dir_bpo_loa_params.DirBpoLoaParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BinaryAPIResponse, ) def list_document_types( @@ -493,7 +581,7 @@ def new_loa( *, phone_numbers: SequenceNotStr[str], agent: AgentInputParam | Omit = omit, - signature: dir_new_loa_params.Signature | Omit = omit, + signature: SignaturePayloadParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -552,6 +640,66 @@ def new_loa( cast_to=BinaryAPIResponse, ) + def retrieve_bpo_authorizations( + self, + dir_id: str, + *, + page_number: int | Omit = omit, + page_size: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DirRetrieveBpoAuthorizationsResponse: + """ + List the BPO (Business Process Outsourcer) accounts a Brand Owner has authorized + on this DIR, together with the review state of each authorization. + + Authorizations are supplied as the `bpo_authorizations` array when creating or + updating a DIR, and each one is reviewed on its own. Only an `approved` + authorization adds that BPO to this DIR's authorized callers in the branded + calling registry; `pending` and `rejected` authorizations do not. Each entry + includes the `loa_document_id` you submitted: because `bpo_authorizations` + replaces the whole list on every DIR update, send each entry you want to keep + back with its `loa_document_id` unchanged, and it keeps its review state. A + rejected entry carries a `rejection_reason`. Returns an empty list when the DIR + has authorized no BPOs. + + Args: + page_number: 1-based page number. Out-of-range values return an empty page with correct meta. + + page_size: Items per page. Maximum 250; values above are clamped to 250. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dir_id: + raise ValueError(f"Expected a non-empty value for `dir_id` but received {dir_id!r}") + return self._get( + path_template("/dir/{dir_id}/bpo_authorizations", dir_id=dir_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page_number": page_number, + "page_size": page_size, + }, + dir_retrieve_bpo_authorizations_params.DirRetrieveBpoAuthorizationsParams, + ), + ), + cast_to=DirRetrieveBpoAuthorizationsResponse, + ) + def submit( self, dir_id: str, @@ -627,12 +775,16 @@ def update_infringement( certify_ip_ownership: Must be `true`. - certify_no_infringement: Must be `true`. + certify_no_infringement: Check to certify that the brand no longer infringes anyone else's trademark or + intellectual property. certify_no_shaft_content: Must be `true`. infringement_resolution_notes: Explanation of how the infringement concern was addressed. + display_name: The business name shown to call recipients, 1 to 35 characters, no emoji, not + blank. + documents: Append-only supporting documents to attach while resolving the claim (e.g. authorization or licensing proof). @@ -769,6 +921,7 @@ async def update( *, authorizer_email: str | Omit = omit, authorizer_name: str | Omit = omit, + bpo_authorizations: Iterable[BpoAuthorizationInputParam] | Omit = omit, call_reasons: SequenceNotStr[str] | Omit = omit, certify_brand_is_accurate: bool | Omit = omit, certify_ip_ownership: bool | Omit = omit, @@ -777,6 +930,7 @@ async def update( documents: Iterable[DocumentParam] | Omit = omit, logo_url: str | Omit = omit, reselling: bool | Omit = omit, + webhook_url: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -789,11 +943,14 @@ async def update( DIRs in `draft`, `rejected`, `unsuccessful`, or `suspended` can be edited freely: PATCH is a pure edit, `status` is never changed, and you re-vet by calling `POST /v2/dir/{dir_id}/submit` explicitly. A `verified` DIR can also - be edited in place: a PATCH that changes any value returns the DIR to `draft` - and branded delivery stops until you re-submit and the DIR is approved again, - while a PATCH that changes nothing (an empty body or values identical to the - current ones) leaves the DIR `verified`, so idempotent retries are safe. DIRs in - any other status (`submitted`, `in_review`, `expired`, `infringement_claimed`, + be edited in place: a PATCH that changes any value returns the DIR to `draft`; + the currently approved identity keeps displaying, and the edited content goes + live only after you re-submit and the DIR is approved again. A PATCH that + changes nothing (an empty body or values identical to the current ones) leaves + the DIR `verified`, so idempotent retries are safe. Changing only + `bpo_authorizations` or `webhook_url` is the exception: the DIR stays + `verified`. Each BPO authorization is reviewed on its own instead. DIRs in any + other status (`submitted`, `in_review`, `expired`, `infringement_claimed`, `permanently_rejected`) cannot be edited. Args: @@ -803,6 +960,13 @@ async def update( authorizer_name: Name of the person at your enterprise authorizing this DIR. Must be a real individual. + bpo_authorizations: Optional. Replace this DIR's authorized BPO (Business Process Outsourcer) + accounts with these, each with its signed Letter of Authorization. The supplied + list replaces the current one: a BPO left out has its authorization removed, and + a new BPO (or a changed Letter of Authorization) is created `pending` admin + review. Send an empty list to clear all authorizations; omit the field to leave + them unchanged. Editing this list does not re-vet the DIR. Maximum 10. + call_reasons: 1–10 reasons your business calls customers. Validate phrasing against `POST /call_reasons/validate`. @@ -827,6 +991,10 @@ async def update( reselling: Set to true if your organization places calls on behalf of other enterprises (BPO/reseller). Updating this triggers re-vetting on next submit. + webhook_url: Optional `https://` URL that receives webhook notifications when this DIR's + compliance review completes. Send `null` to clear. Changing only this field on a + `verified` DIR does not re-vet it. Maximum 2048 characters. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -843,6 +1011,7 @@ async def update( { "authorizer_email": authorizer_email, "authorizer_name": authorizer_name, + "bpo_authorizations": bpo_authorizations, "call_reasons": call_reasons, "certify_brand_is_accurate": certify_brand_is_accurate, "certify_ip_ownership": certify_ip_ownership, @@ -851,6 +1020,7 @@ async def update( "documents": documents, "logo_url": logo_url, "reselling": reselling, + "webhook_url": webhook_url, }, dir_update_params.DirUpdateParams, ), @@ -964,12 +1134,16 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """Delete a DIR. + ) -> DirDeleteResponse: + """Request deletion of a DIR. - Failure modes: `400` if a child phone number is in a non-deletable - status, `409` if the DIR has an unresolved infringement claim, `404` if the DIR - is not yours. + This does not remove the DIR on this call: it records + the request, moves the DIR to `delete_requested`, and Telnyx completes the + removal (de-registration and cleanup) shortly after. A verified DIR keeps + serving its branded identity, and keeps billing, until the removal is executed. + Failure modes: `400` if a child phone number is still attached or the DIR is + `in_review` (wait for the review to finish), `409` if the DIR has an unresolved + infringement claim, `404` if the DIR is not yours. Args: extra_headers: Send extra headers @@ -982,13 +1156,73 @@ async def delete( """ if not dir_id: raise ValueError(f"Expected a non-empty value for `dir_id` but received {dir_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( path_template("/dir/{dir_id}", dir_id=dir_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=DirDeleteResponse, + ) + + async def bpo_loa( + self, + dir_id: str, + *, + bpo_enterprise_id: str, + signature: SignaturePayloadParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncBinaryAPIResponse: + """ + The Letter of Authorization in which a Brand Owner authorizes an approved BPO + (Business Process Outsourcer) to place branded calls that display this DIR on + the owner's behalf. Both parties are read from the caller's account: the Brand + Owner is the enterprise that owns the DIR, and the BPO is `bpo_enterprise_id`. + No business identity is accepted in the body. + + When `signature` is omitted the PDF is returned unsigned so the Brand Owner can + sign it externally and the BPO can upload it via the Documents API. When + `signature` is present the PDF embeds the supplied image, printed name, and + signed-at date. + + Returns `application/pdf`. + + Args: + bpo_enterprise_id: The approved BPO enterprise the Brand Owner is authorizing. Must be a BPO + account on the caller's organization that has already been approved. + + signature: Optional. When provided the rendered PDF embeds the signature image, printed + name, and signed-at date. When absent the PDF is returned unsigned so the Brand + Owner can sign externally and the BPO can upload it via the Documents API. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dir_id: + raise ValueError(f"Expected a non-empty value for `dir_id` but received {dir_id!r}") + extra_headers = {"Accept": "application/pdf", **(extra_headers or {})} + return await self._post( + path_template("/dir/{dir_id}/bpo_loa", dir_id=dir_id), + body=await async_maybe_transform( + { + "bpo_enterprise_id": bpo_enterprise_id, + "signature": signature, + }, + dir_bpo_loa_params.DirBpoLoaParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AsyncBinaryAPIResponse, ) async def list_document_types( @@ -1077,7 +1311,7 @@ async def new_loa( *, phone_numbers: SequenceNotStr[str], agent: AgentInputParam | Omit = omit, - signature: dir_new_loa_params.Signature | Omit = omit, + signature: SignaturePayloadParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1136,6 +1370,66 @@ async def new_loa( cast_to=AsyncBinaryAPIResponse, ) + async def retrieve_bpo_authorizations( + self, + dir_id: str, + *, + page_number: int | Omit = omit, + page_size: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DirRetrieveBpoAuthorizationsResponse: + """ + List the BPO (Business Process Outsourcer) accounts a Brand Owner has authorized + on this DIR, together with the review state of each authorization. + + Authorizations are supplied as the `bpo_authorizations` array when creating or + updating a DIR, and each one is reviewed on its own. Only an `approved` + authorization adds that BPO to this DIR's authorized callers in the branded + calling registry; `pending` and `rejected` authorizations do not. Each entry + includes the `loa_document_id` you submitted: because `bpo_authorizations` + replaces the whole list on every DIR update, send each entry you want to keep + back with its `loa_document_id` unchanged, and it keeps its review state. A + rejected entry carries a `rejection_reason`. Returns an empty list when the DIR + has authorized no BPOs. + + Args: + page_number: 1-based page number. Out-of-range values return an empty page with correct meta. + + page_size: Items per page. Maximum 250; values above are clamped to 250. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dir_id: + raise ValueError(f"Expected a non-empty value for `dir_id` but received {dir_id!r}") + return await self._get( + path_template("/dir/{dir_id}/bpo_authorizations", dir_id=dir_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "page_number": page_number, + "page_size": page_size, + }, + dir_retrieve_bpo_authorizations_params.DirRetrieveBpoAuthorizationsParams, + ), + ), + cast_to=DirRetrieveBpoAuthorizationsResponse, + ) + async def submit( self, dir_id: str, @@ -1211,12 +1505,16 @@ async def update_infringement( certify_ip_ownership: Must be `true`. - certify_no_infringement: Must be `true`. + certify_no_infringement: Check to certify that the brand no longer infringes anyone else's trademark or + intellectual property. certify_no_shaft_content: Must be `true`. infringement_resolution_notes: Explanation of how the infringement concern was addressed. + display_name: The business name shown to call recipients, 1 to 35 characters, no emoji, not + blank. + documents: Append-only supporting documents to attach while resolving the claim (e.g. authorization or licensing proof). @@ -1271,6 +1569,10 @@ def __init__(self, dir: DirResource) -> None: self.delete = to_raw_response_wrapper( dir.delete, ) + self.bpo_loa = to_custom_raw_response_wrapper( + dir.bpo_loa, + BinaryAPIResponse, + ) self.list_document_types = to_raw_response_wrapper( dir.list_document_types, ) @@ -1281,6 +1583,9 @@ def __init__(self, dir: DirResource) -> None: dir.new_loa, BinaryAPIResponse, ) + self.retrieve_bpo_authorizations = to_raw_response_wrapper( + dir.retrieve_bpo_authorizations, + ) self.submit = to_raw_response_wrapper( dir.submit, ) @@ -1342,6 +1647,10 @@ def __init__(self, dir: AsyncDirResource) -> None: self.delete = async_to_raw_response_wrapper( dir.delete, ) + self.bpo_loa = async_to_custom_raw_response_wrapper( + dir.bpo_loa, + AsyncBinaryAPIResponse, + ) self.list_document_types = async_to_raw_response_wrapper( dir.list_document_types, ) @@ -1352,6 +1661,9 @@ def __init__(self, dir: AsyncDirResource) -> None: dir.new_loa, AsyncBinaryAPIResponse, ) + self.retrieve_bpo_authorizations = async_to_raw_response_wrapper( + dir.retrieve_bpo_authorizations, + ) self.submit = async_to_raw_response_wrapper( dir.submit, ) @@ -1413,6 +1725,10 @@ def __init__(self, dir: DirResource) -> None: self.delete = to_streamed_response_wrapper( dir.delete, ) + self.bpo_loa = to_custom_streamed_response_wrapper( + dir.bpo_loa, + StreamedBinaryAPIResponse, + ) self.list_document_types = to_streamed_response_wrapper( dir.list_document_types, ) @@ -1423,6 +1739,9 @@ def __init__(self, dir: DirResource) -> None: dir.new_loa, StreamedBinaryAPIResponse, ) + self.retrieve_bpo_authorizations = to_streamed_response_wrapper( + dir.retrieve_bpo_authorizations, + ) self.submit = to_streamed_response_wrapper( dir.submit, ) @@ -1484,6 +1803,10 @@ def __init__(self, dir: AsyncDirResource) -> None: self.delete = async_to_streamed_response_wrapper( dir.delete, ) + self.bpo_loa = async_to_custom_streamed_response_wrapper( + dir.bpo_loa, + AsyncStreamedBinaryAPIResponse, + ) self.list_document_types = async_to_streamed_response_wrapper( dir.list_document_types, ) @@ -1494,6 +1817,9 @@ def __init__(self, dir: AsyncDirResource) -> None: dir.new_loa, AsyncStreamedBinaryAPIResponse, ) + self.retrieve_bpo_authorizations = async_to_streamed_response_wrapper( + dir.retrieve_bpo_authorizations, + ) self.submit = async_to_streamed_response_wrapper( dir.submit, ) diff --git a/src/telnyx/resources/dir/phone_numbers.py b/src/telnyx/resources/dir/phone_numbers.py index 99828d3bc..ac9113d42 100644 --- a/src/telnyx/resources/dir/phone_numbers.py +++ b/src/telnyx/resources/dir/phone_numbers.py @@ -129,11 +129,11 @@ def add( """Register phone numbers under a DIR. The enterprise is resolved server-side from - the DIR id. Same body, failure modes, and batch semantics whichever path form - you use. + the DIR id. - **Pricing:** This is a billable action. See https://telnyx.com/pricing/numbers - for current pricing. + **Pricing:** Adding phone numbers is free. Branded Calling fees are charged per + DIR and per branded call. See https://telnyx.com/pricing/branded-calling for + current pricing. Args: documents: Supporting documents covering this batch. At least one entry with @@ -188,6 +188,9 @@ def remove( the DIR id. Returns a partial-success envelope. Args: + phone_numbers: The phone numbers to remove from this brand, in E.164 format, up to 100 per + request. They must currently be attached to this brand. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -304,11 +307,11 @@ async def add( """Register phone numbers under a DIR. The enterprise is resolved server-side from - the DIR id. Same body, failure modes, and batch semantics whichever path form - you use. + the DIR id. - **Pricing:** This is a billable action. See https://telnyx.com/pricing/numbers - for current pricing. + **Pricing:** Adding phone numbers is free. Branded Calling fees are charged per + DIR and per branded call. See https://telnyx.com/pricing/branded-calling for + current pricing. Args: documents: Supporting documents covering this batch. At least one entry with @@ -363,6 +366,9 @@ async def remove( the DIR id. Returns a partial-success envelope. Args: + phone_numbers: The phone numbers to remove from this brand, in E.164 format, up to 100 per + request. They must currently be attached to this brand. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/telnyx/resources/dir/references.py b/src/telnyx/resources/dir/references.py index 0383d923c..8bed4a49f 100644 --- a/src/telnyx/resources/dir/references.py +++ b/src/telnyx/resources/dir/references.py @@ -162,19 +162,22 @@ def update( pending call into the new local calling window. Args: - email: Reference contact email address. + email: The reference's email address. We email them scheduling and dial-in instructions + before we call, so use an address they check. - full_name: Full name of the reference contact. + full_name: The full name of the person we should contact as your reference. - job_title: Job title of the reference contact. + job_title: The reference contact's job title, for example CFO or Owner. - organization: Organization the reference contact belongs to. + organization: The name of the organization the reference contact works for. - phone_e164: Reference phone number in E.164 format. + phone_e164: The reference's phone number in E.164 format, for example +14155550123. We call + this number during their local business hours. relationship_to_registrant: How the reference contact is related to the registering business. - timezone: IANA timezone id for the reference. + timezone: The reference's IANA time zone, for example America/New_York. We only call + during their local 8am to 9pm hours, which is why we need it. extra_headers: Send extra headers @@ -385,19 +388,22 @@ async def update( pending call into the new local calling window. Args: - email: Reference contact email address. + email: The reference's email address. We email them scheduling and dial-in instructions + before we call, so use an address they check. - full_name: Full name of the reference contact. + full_name: The full name of the person we should contact as your reference. - job_title: Job title of the reference contact. + job_title: The reference contact's job title, for example CFO or Owner. - organization: Organization the reference contact belongs to. + organization: The name of the organization the reference contact works for. - phone_e164: Reference phone number in E.164 format. + phone_e164: The reference's phone number in E.164 format, for example +14155550123. We call + this number during their local business hours. relationship_to_registrant: How the reference contact is related to the registering business. - timezone: IANA timezone id for the reference. + timezone: The reference's IANA time zone, for example America/New_York. We only call + during their local 8am to 9pm hours, which is why we need it. extra_headers: Send extra headers diff --git a/src/telnyx/resources/enterprises/__init__.py b/src/telnyx/resources/enterprises/__init__.py index f3f33c7f2..116d6a3a1 100644 --- a/src/telnyx/resources/enterprises/__init__.py +++ b/src/telnyx/resources/enterprises/__init__.py @@ -24,6 +24,14 @@ EnterprisesResourceWithStreamingResponse, AsyncEnterprisesResourceWithStreamingResponse, ) +from .verify_email import ( + VerifyEmailResource, + AsyncVerifyEmailResource, + VerifyEmailResourceWithRawResponse, + AsyncVerifyEmailResourceWithRawResponse, + VerifyEmailResourceWithStreamingResponse, + AsyncVerifyEmailResourceWithStreamingResponse, +) __all__ = [ "ReputationResource", @@ -38,6 +46,12 @@ "AsyncDirResourceWithRawResponse", "DirResourceWithStreamingResponse", "AsyncDirResourceWithStreamingResponse", + "VerifyEmailResource", + "AsyncVerifyEmailResource", + "VerifyEmailResourceWithRawResponse", + "AsyncVerifyEmailResourceWithRawResponse", + "VerifyEmailResourceWithStreamingResponse", + "AsyncVerifyEmailResourceWithStreamingResponse", "EnterprisesResource", "AsyncEnterprisesResource", "EnterprisesResourceWithRawResponse", diff --git a/src/telnyx/resources/enterprises/dir.py b/src/telnyx/resources/enterprises/dir.py index adf663033..8bc75eebd 100644 --- a/src/telnyx/resources/enterprises/dir.py +++ b/src/telnyx/resources/enterprises/dir.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Union, Iterable +from typing import Union, Iterable, Optional from datetime import datetime from typing_extensions import Literal @@ -26,6 +26,7 @@ from ...types.dir_wrapped import DirWrapped from ...types.enterprises import dir_list_params, dir_create_params from ...types.document_param import DocumentParam +from ...types.bpo_authorization_input_param import BpoAuthorizationInputParam __all__ = ["DirResource", "AsyncDirResource"] @@ -65,9 +66,11 @@ def create( certify_ip_ownership: Literal[True], certify_no_shaft_content: Literal[True], display_name: str, + bpo_authorizations: Iterable[BpoAuthorizationInputParam] | Omit = omit, documents: Iterable[DocumentParam] | Omit = omit, logo_url: str | Omit = omit, reselling: bool | Omit = omit, + webhook_url: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -113,7 +116,8 @@ def create( call_reasons: 1–10 reasons your business calls customers. Validate phrasing against `POST /call_reasons/validate`. - certify_brand_is_accurate: Must be `true`. + certify_brand_is_accurate: Certification that the DIR information is accurate. Must be `true` for the DIR + to be submitted for vetting. certify_ip_ownership: Must be `true`. Confirms ownership of any logos/trademarks shown. @@ -122,6 +126,13 @@ def create( display_name: Name shown to call recipients. No emoji; not whitespace-only. + bpo_authorizations: Optional. Approved BPO (Business Process Outsourcer) accounts on your + organization authorized to place branded calls for this DIR, each with the + signed Letter of Authorization the Brand Owner granted it. Each authorization + starts `pending` and takes effect only after an admin reviews its Letter of + Authorization. Omit or send an empty list to authorize no BPO on this DIR. + Maximum 10. + documents: Supporting documents. Each `document_id` may appear at most once on a DIR. logo_url: Publicly accessible HTTPS URL (max 128 chars) to a 256x256 BMP logo (max 1 MB). @@ -129,6 +140,10 @@ def create( reselling: Set to true if your organization places calls on behalf of other enterprises (BPO/reseller). + webhook_url: Optional `https://` URL that receives webhook notifications when this DIR's + compliance review completes (rejection outcomes include structured rejection + reasons). Maximum 2048 characters. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -150,9 +165,11 @@ def create( "certify_ip_ownership": certify_ip_ownership, "certify_no_shaft_content": certify_no_shaft_content, "display_name": display_name, + "bpo_authorizations": bpo_authorizations, "documents": documents, "logo_url": logo_url, "reselling": reselling, + "webhook_url": webhook_url, }, dir_create_params.DirCreateParams, ), @@ -305,9 +322,11 @@ async def create( certify_ip_ownership: Literal[True], certify_no_shaft_content: Literal[True], display_name: str, + bpo_authorizations: Iterable[BpoAuthorizationInputParam] | Omit = omit, documents: Iterable[DocumentParam] | Omit = omit, logo_url: str | Omit = omit, reselling: bool | Omit = omit, + webhook_url: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -353,7 +372,8 @@ async def create( call_reasons: 1–10 reasons your business calls customers. Validate phrasing against `POST /call_reasons/validate`. - certify_brand_is_accurate: Must be `true`. + certify_brand_is_accurate: Certification that the DIR information is accurate. Must be `true` for the DIR + to be submitted for vetting. certify_ip_ownership: Must be `true`. Confirms ownership of any logos/trademarks shown. @@ -362,6 +382,13 @@ async def create( display_name: Name shown to call recipients. No emoji; not whitespace-only. + bpo_authorizations: Optional. Approved BPO (Business Process Outsourcer) accounts on your + organization authorized to place branded calls for this DIR, each with the + signed Letter of Authorization the Brand Owner granted it. Each authorization + starts `pending` and takes effect only after an admin reviews its Letter of + Authorization. Omit or send an empty list to authorize no BPO on this DIR. + Maximum 10. + documents: Supporting documents. Each `document_id` may appear at most once on a DIR. logo_url: Publicly accessible HTTPS URL (max 128 chars) to a 256x256 BMP logo (max 1 MB). @@ -369,6 +396,10 @@ async def create( reselling: Set to true if your organization places calls on behalf of other enterprises (BPO/reseller). + webhook_url: Optional `https://` URL that receives webhook notifications when this DIR's + compliance review completes (rejection outcomes include structured rejection + reasons). Maximum 2048 characters. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -390,9 +421,11 @@ async def create( "certify_ip_ownership": certify_ip_ownership, "certify_no_shaft_content": certify_no_shaft_content, "display_name": display_name, + "bpo_authorizations": bpo_authorizations, "documents": documents, "logo_url": logo_url, "reselling": reselling, + "webhook_url": webhook_url, }, dir_create_params.DirCreateParams, ), diff --git a/src/telnyx/resources/enterprises/enterprises.py b/src/telnyx/resources/enterprises/enterprises.py index 457d3b00d..f003af233 100644 --- a/src/telnyx/resources/enterprises/enterprises.py +++ b/src/telnyx/resources/enterprises/enterprises.py @@ -31,6 +31,14 @@ async_to_streamed_response_wrapper, ) from ...pagination import SyncDefaultFlatPagination, AsyncDefaultFlatPagination +from .verify_email import ( + VerifyEmailResource, + AsyncVerifyEmailResource, + VerifyEmailResourceWithRawResponse, + AsyncVerifyEmailResourceWithRawResponse, + VerifyEmailResourceWithStreamingResponse, + AsyncVerifyEmailResourceWithStreamingResponse, +) from ..._base_client import AsyncPaginator, make_request_options from .reputation.reputation import ( ReputationResource, @@ -64,6 +72,14 @@ def dir(self) -> DirResource: """ return DirResource(self._client) + @cached_property + def verify_email(self) -> VerifyEmailResource: + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + return VerifyEmailResource(self._client) + @cached_property def with_raw_response(self) -> EnterprisesResourceWithRawResponse: """ @@ -175,11 +191,20 @@ def create( Args: country_code: ISO 3166-1 alpha-2 country code. Currently `US` and `CA` are supported. + doing_business_as: The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + fein: US Federal Employer Identification Number (`NN-NNNNNNN`) or Canadian equivalent. - industry: Industry classification. + industry: The industry your business operates in. Choose the closest match from the list; + if your value is not accepted, pick the nearest category. - legal_name: Legal name of the enterprise. + jurisdiction_of_incorporation: The state, province, or country where your business was legally incorporated, + for example Delaware. + + legal_name: Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. number_of_employees: Approximate headcount range. Used for vetting heuristics; pick the bucket that contains your current employee count. @@ -204,19 +229,35 @@ def create( - `non_profit` - registered 501(c)(3)/equivalent (incl. educational institutions, charities, religious organisations). - corporate_registration_number: Optional corporate-registration / company-number identifier. + website: Your business's public website address, including https://. Leave blank if your + business has no website. + + corporate_registration_number: The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. - customer_reference: Optional free-form string the caller can attach for their own bookkeeping. - Telnyx does not interpret it. + customer_reference: Your own label for this account. Enter any reference that helps you find it in + your records. Telnyx does not use it during vetting. - dun_bradstreet_number: Optional D-U-N-S Number. + dun_bradstreet_number: Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. - primary_business_domain_sic_code: Optional SIC code for the primary line of business. + primary_business_domain_sic_code: The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. - professional_license_number: Optional professional-license number for regulated industries. + professional_license_number: If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. - role_type: `enterprise` for an organization registering its own DIRs; `bpo` for a Business - Process Outsourcer placing calls on behalf of one or more enterprises. + role_type: `enterprise` for an organization registering its own DIRs (the default, and the + right choice when the calls display your own brand). `bpo` for a Business + Process Outsourcer: a call center that places calls on behalf of other + enterprises and displays their brand. A `bpo` enterprise describes the call + center itself and cannot own a DIR. Each client the call center calls for gets + its own `enterprise` in the same account, with the client's DIR under it; that + DIR is then linked to the `bpo` enterprise through `bpo_authorizations`. Fixed + at creation. extra_headers: Send extra headers @@ -375,10 +416,71 @@ def update( cannot be changed: including any of them in the body is rejected with `400 Bad Request` (`Field 'X' is not allowed in this request`). + For an approved BPO enterprise (`role_type` `bpo`), changing any identity field + (legal name, DBA, website, FEIN, industry, number of employees, physical + address, organization contact, D-U-N-S number, legal type, SIC code, corporate + registration number, professional license number, or jurisdiction of + incorporation) resets `bpo_verification_status` to `pending` for re-approval and + sets every DIR authorization for that BPO to `rejected`. After re-approval, link + it again with a newly signed LOA (a new `loa_document_id`); resending the old + one keeps the authorization `rejected`. Re-sending an unchanged value does not + reset anything. + + If Number Reputation is enabled on the enterprise, `legal_name`, + `doing_business_as`, `website`, `fein`, `industry`, `number_of_employees`, + `organization_physical_address`, `organization_contact`, and + `dun_bradstreet_number` cannot be changed: the request is rejected with `400`. + Args: - jurisdiction_of_incorporation: Updated state/province/country of incorporation. Optional on update. + corporate_registration_number: The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. + + customer_reference: Your own label for this account. Enter any reference that helps you find it in + your records. Telnyx does not use it during vetting. + + doing_business_as: The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + + dun_bradstreet_number: Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. + + fein: US Federal Employer Identification Number (`NN-NNNNNNN`) or Canadian equivalent. + + industry: The industry your business operates in. Choose the closest match from the list; + if your value is not accepted, pick the nearest category. + + jurisdiction_of_incorporation: The state, province, or country where your business was legally incorporated, + for example Delaware. + + legal_name: Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. + + number_of_employees: Approximate headcount range. Used for vetting heuristics; pick the bucket that + contains your current employee count. + + organization_legal_type: + Legal-entity form. Pick the form that matches your incorporation documents: + + - `corporation` - C-corp or S-corp. + - `llc` - limited liability company. + - `partnership` - general/limited partnership. + - `nonprofit` - non-profit corporation, charitable trust, or + 501(c)(3)/equivalent. + - `other` - anything else (sole proprietorships, government bodies, DBAs, etc.). + You may be asked for additional documents during vetting. + + primary_business_domain_sic_code: The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. + + professional_license_number: If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. - legal_name: Legal name of the enterprise. + website: Your business's public website address, including https://. Leave blank if your + business has no website. extra_headers: Send extra headers @@ -424,6 +526,7 @@ def list( self, *, filter_legal_name_contains: str | Omit = omit, + filter_role_type: Literal["enterprise", "bpo"] | Omit = omit, legal_name: str | Omit = omit, page_number: int | Omit = omit, page_size: int | Omit = omit, @@ -442,6 +545,9 @@ def list( Args: filter_legal_name_contains: Case-insensitive partial match on legal name. + filter_role_type: Only return enterprises of this type: `bpo` for call-center (BPO) enterprises, + `enterprise` for normal enterprises. Omit to return both. + legal_name: Filter by legal name (partial match). page_number: 1-based page number. Out-of-range values return an empty page with correct meta. @@ -467,6 +573,7 @@ def list( query=maybe_transform( { "filter_legal_name_contains": filter_legal_name_contains, + "filter_role_type": filter_role_type, "legal_name": legal_name, "page_number": page_number, "page_size": page_size, @@ -531,8 +638,8 @@ def branded_calling( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EnterprisePublicWrapped: - """ - Branded Calling is a paid product that must be activated on each enterprise. + """Branded Calling must be activated on each enterprise. + Activation is idempotent: - First call: marks the enterprise as activated and begins onboarding it with @@ -547,11 +654,15 @@ def branded_calling( Failure modes: + - `400` - the account has no available credit. Add funds and retry. + - `400` - the enterprise is not in the United States. Branded Calling is + currently available only to US enterprises. - `403` - Branded Calling Terms of Service not accepted. - `404` - enterprise does not exist or does not belong to your account. - **Pricing:** This is a billable action. See https://telnyx.com/pricing/numbers - for current pricing. + **Pricing:** Activation itself is free, but the account must have available + credit. Branded Calling fees are charged per DIR and per branded call. See + https://telnyx.com/pricing/branded-calling for current pricing. Args: extra_headers: Send extra headers @@ -588,6 +699,14 @@ def dir(self) -> AsyncDirResource: """ return AsyncDirResource(self._client) + @cached_property + def verify_email(self) -> AsyncVerifyEmailResource: + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + return AsyncVerifyEmailResource(self._client) + @cached_property def with_raw_response(self) -> AsyncEnterprisesResourceWithRawResponse: """ @@ -699,11 +818,20 @@ async def create( Args: country_code: ISO 3166-1 alpha-2 country code. Currently `US` and `CA` are supported. + doing_business_as: The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + fein: US Federal Employer Identification Number (`NN-NNNNNNN`) or Canadian equivalent. - industry: Industry classification. + industry: The industry your business operates in. Choose the closest match from the list; + if your value is not accepted, pick the nearest category. + + jurisdiction_of_incorporation: The state, province, or country where your business was legally incorporated, + for example Delaware. - legal_name: Legal name of the enterprise. + legal_name: Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. number_of_employees: Approximate headcount range. Used for vetting heuristics; pick the bucket that contains your current employee count. @@ -728,19 +856,35 @@ async def create( - `non_profit` - registered 501(c)(3)/equivalent (incl. educational institutions, charities, religious organisations). - corporate_registration_number: Optional corporate-registration / company-number identifier. + website: Your business's public website address, including https://. Leave blank if your + business has no website. - customer_reference: Optional free-form string the caller can attach for their own bookkeeping. - Telnyx does not interpret it. + corporate_registration_number: The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. - dun_bradstreet_number: Optional D-U-N-S Number. + customer_reference: Your own label for this account. Enter any reference that helps you find it in + your records. Telnyx does not use it during vetting. - primary_business_domain_sic_code: Optional SIC code for the primary line of business. + dun_bradstreet_number: Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. - professional_license_number: Optional professional-license number for regulated industries. + primary_business_domain_sic_code: The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. - role_type: `enterprise` for an organization registering its own DIRs; `bpo` for a Business - Process Outsourcer placing calls on behalf of one or more enterprises. + professional_license_number: If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. + + role_type: `enterprise` for an organization registering its own DIRs (the default, and the + right choice when the calls display your own brand). `bpo` for a Business + Process Outsourcer: a call center that places calls on behalf of other + enterprises and displays their brand. A `bpo` enterprise describes the call + center itself and cannot own a DIR. Each client the call center calls for gets + its own `enterprise` in the same account, with the client's DIR under it; that + DIR is then linked to the `bpo` enterprise through `bpo_authorizations`. Fixed + at creation. extra_headers: Send extra headers @@ -899,10 +1043,71 @@ async def update( cannot be changed: including any of them in the body is rejected with `400 Bad Request` (`Field 'X' is not allowed in this request`). + For an approved BPO enterprise (`role_type` `bpo`), changing any identity field + (legal name, DBA, website, FEIN, industry, number of employees, physical + address, organization contact, D-U-N-S number, legal type, SIC code, corporate + registration number, professional license number, or jurisdiction of + incorporation) resets `bpo_verification_status` to `pending` for re-approval and + sets every DIR authorization for that BPO to `rejected`. After re-approval, link + it again with a newly signed LOA (a new `loa_document_id`); resending the old + one keeps the authorization `rejected`. Re-sending an unchanged value does not + reset anything. + + If Number Reputation is enabled on the enterprise, `legal_name`, + `doing_business_as`, `website`, `fein`, `industry`, `number_of_employees`, + `organization_physical_address`, `organization_contact`, and + `dun_bradstreet_number` cannot be changed: the request is rejected with `400`. + Args: - jurisdiction_of_incorporation: Updated state/province/country of incorporation. Optional on update. + corporate_registration_number: The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. - legal_name: Legal name of the enterprise. + customer_reference: Your own label for this account. Enter any reference that helps you find it in + your records. Telnyx does not use it during vetting. + + doing_business_as: The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + + dun_bradstreet_number: Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. + + fein: US Federal Employer Identification Number (`NN-NNNNNNN`) or Canadian equivalent. + + industry: The industry your business operates in. Choose the closest match from the list; + if your value is not accepted, pick the nearest category. + + jurisdiction_of_incorporation: The state, province, or country where your business was legally incorporated, + for example Delaware. + + legal_name: Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. + + number_of_employees: Approximate headcount range. Used for vetting heuristics; pick the bucket that + contains your current employee count. + + organization_legal_type: + Legal-entity form. Pick the form that matches your incorporation documents: + + - `corporation` - C-corp or S-corp. + - `llc` - limited liability company. + - `partnership` - general/limited partnership. + - `nonprofit` - non-profit corporation, charitable trust, or + 501(c)(3)/equivalent. + - `other` - anything else (sole proprietorships, government bodies, DBAs, etc.). + You may be asked for additional documents during vetting. + + primary_business_domain_sic_code: The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. + + professional_license_number: If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. + + website: Your business's public website address, including https://. Leave blank if your + business has no website. extra_headers: Send extra headers @@ -948,6 +1153,7 @@ def list( self, *, filter_legal_name_contains: str | Omit = omit, + filter_role_type: Literal["enterprise", "bpo"] | Omit = omit, legal_name: str | Omit = omit, page_number: int | Omit = omit, page_size: int | Omit = omit, @@ -966,6 +1172,9 @@ def list( Args: filter_legal_name_contains: Case-insensitive partial match on legal name. + filter_role_type: Only return enterprises of this type: `bpo` for call-center (BPO) enterprises, + `enterprise` for normal enterprises. Omit to return both. + legal_name: Filter by legal name (partial match). page_number: 1-based page number. Out-of-range values return an empty page with correct meta. @@ -991,6 +1200,7 @@ def list( query=maybe_transform( { "filter_legal_name_contains": filter_legal_name_contains, + "filter_role_type": filter_role_type, "legal_name": legal_name, "page_number": page_number, "page_size": page_size, @@ -1055,8 +1265,8 @@ async def branded_calling( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EnterprisePublicWrapped: - """ - Branded Calling is a paid product that must be activated on each enterprise. + """Branded Calling must be activated on each enterprise. + Activation is idempotent: - First call: marks the enterprise as activated and begins onboarding it with @@ -1071,11 +1281,15 @@ async def branded_calling( Failure modes: + - `400` - the account has no available credit. Add funds and retry. + - `400` - the enterprise is not in the United States. Branded Calling is + currently available only to US enterprises. - `403` - Branded Calling Terms of Service not accepted. - `404` - enterprise does not exist or does not belong to your account. - **Pricing:** This is a billable action. See https://telnyx.com/pricing/numbers - for current pricing. + **Pricing:** Activation itself is free, but the account must have available + credit. Branded Calling fees are charged per DIR and per branded call. See + https://telnyx.com/pricing/branded-calling for current pricing. Args: extra_headers: Send extra headers @@ -1132,6 +1346,14 @@ def dir(self) -> DirResourceWithRawResponse: """ return DirResourceWithRawResponse(self._enterprises.dir) + @cached_property + def verify_email(self) -> VerifyEmailResourceWithRawResponse: + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + return VerifyEmailResourceWithRawResponse(self._enterprises.verify_email) + class AsyncEnterprisesResourceWithRawResponse: def __init__(self, enterprises: AsyncEnterprisesResource) -> None: @@ -1168,6 +1390,14 @@ def dir(self) -> AsyncDirResourceWithRawResponse: """ return AsyncDirResourceWithRawResponse(self._enterprises.dir) + @cached_property + def verify_email(self) -> AsyncVerifyEmailResourceWithRawResponse: + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + return AsyncVerifyEmailResourceWithRawResponse(self._enterprises.verify_email) + class EnterprisesResourceWithStreamingResponse: def __init__(self, enterprises: EnterprisesResource) -> None: @@ -1204,6 +1434,14 @@ def dir(self) -> DirResourceWithStreamingResponse: """ return DirResourceWithStreamingResponse(self._enterprises.dir) + @cached_property + def verify_email(self) -> VerifyEmailResourceWithStreamingResponse: + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + return VerifyEmailResourceWithStreamingResponse(self._enterprises.verify_email) + class AsyncEnterprisesResourceWithStreamingResponse: def __init__(self, enterprises: AsyncEnterprisesResource) -> None: @@ -1239,3 +1477,11 @@ def dir(self) -> AsyncDirResourceWithStreamingResponse: A Display Identity Record (DIR) is the verified calling identity (display name, logo, call reasons) shown to recipients on outbound calls. """ return AsyncDirResourceWithStreamingResponse(self._enterprises.dir) + + @cached_property + def verify_email(self) -> AsyncVerifyEmailResourceWithStreamingResponse: + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + return AsyncVerifyEmailResourceWithStreamingResponse(self._enterprises.verify_email) diff --git a/src/telnyx/resources/enterprises/reputation/loa.py b/src/telnyx/resources/enterprises/reputation/loa.py index 1c99019fe..b2c4a686e 100644 --- a/src/telnyx/resources/enterprises/reputation/loa.py +++ b/src/telnyx/resources/enterprises/reputation/loa.py @@ -24,7 +24,6 @@ ) from ...._base_client import make_request_options from ....types.enterprises.reputation import loa_render_params, loa_update_params -from ....types.enterprises.reputation.agent_input_param import AgentInputParam from ....types.enterprises.enterprise_reputation_public_wrapped import EnterpriseReputationPublicWrapped __all__ = ["LoaResource", "AsyncLoaResource"] @@ -98,7 +97,7 @@ def render( self, enterprise_id: str, *, - agent: AgentInputParam | Omit = omit, + agent: loa_render_params.Agent | Omit = omit, signature: loa_render_params.Signature | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -219,7 +218,7 @@ async def render( self, enterprise_id: str, *, - agent: AgentInputParam | Omit = omit, + agent: loa_render_params.Agent | Omit = omit, signature: loa_render_params.Signature | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. diff --git a/src/telnyx/resources/enterprises/verify_email.py b/src/telnyx/resources/enterprises/verify_email.py new file mode 100644 index 000000000..7858d7597 --- /dev/null +++ b/src/telnyx/resources/enterprises/verify_email.py @@ -0,0 +1,287 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.enterprises import verify_email_confirm_params +from ...types.enterprises.enterprise_email_verification_status_wrapped import EnterpriseEmailVerificationStatusWrapped + +__all__ = ["VerifyEmailResource", "AsyncVerifyEmailResource"] + + +class VerifyEmailResource(SyncAPIResource): + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + + @cached_property + def with_raw_response(self) -> VerifyEmailResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#accessing-raw-response-data-eg-headers + """ + return VerifyEmailResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VerifyEmailResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#with_streaming_response + """ + return VerifyEmailResourceWithStreamingResponse(self) + + def create( + self, + enterprise_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnterpriseEmailVerificationStatusWrapped: + """ + Email a 6-digit code to the enterprise account's contact email to confirm + ownership of that address. + + A BPO (Business Process Outsourcer) account has no DIR, so it proves ownership + of its own contact email here rather than through a DIR. A BPO account cannot be + approved for use until this contact email is verified. + + The code expires in 15 minutes. Requesting a new code invalidates any previous + one. Resends are rate limited (a short cooldown plus a daily cap). Submit the + code to `POST /enterprises/{enterprise_id}/verify_email/confirm`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not enterprise_id: + raise ValueError(f"Expected a non-empty value for `enterprise_id` but received {enterprise_id!r}") + return self._post( + path_template("/enterprises/{enterprise_id}/verify_email", enterprise_id=enterprise_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnterpriseEmailVerificationStatusWrapped, + ) + + def confirm( + self, + enterprise_id: str, + *, + code: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnterpriseEmailVerificationStatusWrapped: + """ + Submit the 6-digit code that was emailed to the enterprise account's contact + email. On success the contact email is marked verified. + + For security, any failure (wrong, expired, already-used, or too many attempts) + returns the same generic message. + + Args: + code: The 6-digit code sent to the enterprise account's contact email. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not enterprise_id: + raise ValueError(f"Expected a non-empty value for `enterprise_id` but received {enterprise_id!r}") + return self._post( + path_template("/enterprises/{enterprise_id}/verify_email/confirm", enterprise_id=enterprise_id), + body=maybe_transform({"code": code}, verify_email_confirm_params.VerifyEmailConfirmParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnterpriseEmailVerificationStatusWrapped, + ) + + +class AsyncVerifyEmailResource(AsyncAPIResource): + """Verify ownership of a DIR's authorizer email. + + A short code is emailed and confirmed; the email must be verified before references can be submitted. + """ + + @cached_property + def with_raw_response(self) -> AsyncVerifyEmailResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#accessing-raw-response-data-eg-headers + """ + return AsyncVerifyEmailResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVerifyEmailResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#with_streaming_response + """ + return AsyncVerifyEmailResourceWithStreamingResponse(self) + + async def create( + self, + enterprise_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnterpriseEmailVerificationStatusWrapped: + """ + Email a 6-digit code to the enterprise account's contact email to confirm + ownership of that address. + + A BPO (Business Process Outsourcer) account has no DIR, so it proves ownership + of its own contact email here rather than through a DIR. A BPO account cannot be + approved for use until this contact email is verified. + + The code expires in 15 minutes. Requesting a new code invalidates any previous + one. Resends are rate limited (a short cooldown plus a daily cap). Submit the + code to `POST /enterprises/{enterprise_id}/verify_email/confirm`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not enterprise_id: + raise ValueError(f"Expected a non-empty value for `enterprise_id` but received {enterprise_id!r}") + return await self._post( + path_template("/enterprises/{enterprise_id}/verify_email", enterprise_id=enterprise_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnterpriseEmailVerificationStatusWrapped, + ) + + async def confirm( + self, + enterprise_id: str, + *, + code: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnterpriseEmailVerificationStatusWrapped: + """ + Submit the 6-digit code that was emailed to the enterprise account's contact + email. On success the contact email is marked verified. + + For security, any failure (wrong, expired, already-used, or too many attempts) + returns the same generic message. + + Args: + code: The 6-digit code sent to the enterprise account's contact email. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not enterprise_id: + raise ValueError(f"Expected a non-empty value for `enterprise_id` but received {enterprise_id!r}") + return await self._post( + path_template("/enterprises/{enterprise_id}/verify_email/confirm", enterprise_id=enterprise_id), + body=await async_maybe_transform({"code": code}, verify_email_confirm_params.VerifyEmailConfirmParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnterpriseEmailVerificationStatusWrapped, + ) + + +class VerifyEmailResourceWithRawResponse: + def __init__(self, verify_email: VerifyEmailResource) -> None: + self._verify_email = verify_email + + self.create = to_raw_response_wrapper( + verify_email.create, + ) + self.confirm = to_raw_response_wrapper( + verify_email.confirm, + ) + + +class AsyncVerifyEmailResourceWithRawResponse: + def __init__(self, verify_email: AsyncVerifyEmailResource) -> None: + self._verify_email = verify_email + + self.create = async_to_raw_response_wrapper( + verify_email.create, + ) + self.confirm = async_to_raw_response_wrapper( + verify_email.confirm, + ) + + +class VerifyEmailResourceWithStreamingResponse: + def __init__(self, verify_email: VerifyEmailResource) -> None: + self._verify_email = verify_email + + self.create = to_streamed_response_wrapper( + verify_email.create, + ) + self.confirm = to_streamed_response_wrapper( + verify_email.confirm, + ) + + +class AsyncVerifyEmailResourceWithStreamingResponse: + def __init__(self, verify_email: AsyncVerifyEmailResource) -> None: + self._verify_email = verify_email + + self.create = async_to_streamed_response_wrapper( + verify_email.create, + ) + self.confirm = async_to_streamed_response_wrapper( + verify_email.confirm, + ) diff --git a/src/telnyx/resources/meeting_sessions/meeting_sessions.py b/src/telnyx/resources/meeting_sessions/meeting_sessions.py index b85190e01..05ee3487b 100644 --- a/src/telnyx/resources/meeting_sessions/meeting_sessions.py +++ b/src/telnyx/resources/meeting_sessions/meeting_sessions.py @@ -382,12 +382,12 @@ def delete_recording_media( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MeetingSessionDeleteRecordingMediaResponse: """ - Irreversibly requests deletion of provider-hosted aggregate recording media - under the provider contract. The operation retains the Telnyx-local Meeting - session, transcript segments, events, artifacts, and usage records. It is - separate from `DELETE /meeting_sessions/{id}`, which stops or cancels - participation without deleting the persisted session. A missing/foreign session - returns 404; provider deletion failures return 502. + Irreversibly requests deletion of the aggregate recording media for the session. + The operation retains the Telnyx-local Meeting session, transcript segments, + events, artifacts, and usage records. It is separate from + `DELETE /meeting_sessions/{id}`, which stops or cancels participation without + deleting the persisted session. A missing/foreign session returns 404; provider + deletion failures return 502. Args: extra_headers: Send extra headers @@ -886,12 +886,12 @@ async def delete_recording_media( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MeetingSessionDeleteRecordingMediaResponse: """ - Irreversibly requests deletion of provider-hosted aggregate recording media - under the provider contract. The operation retains the Telnyx-local Meeting - session, transcript segments, events, artifacts, and usage records. It is - separate from `DELETE /meeting_sessions/{id}`, which stops or cancels - participation without deleting the persisted session. A missing/foreign session - returns 404; provider deletion failures return 502. + Irreversibly requests deletion of the aggregate recording media for the session. + The operation retains the Telnyx-local Meeting session, transcript segments, + events, artifacts, and usage records. It is separate from + `DELETE /meeting_sessions/{id}`, which stops or cancels participation without + deleting the persisted session. A missing/foreign session returns 404; provider + deletion failures return 502. Args: extra_headers: Send extra headers diff --git a/src/telnyx/resources/spend_limits.py b/src/telnyx/resources/spend_limits.py new file mode 100644 index 000000000..65b997d58 --- /dev/null +++ b/src/telnyx/resources/spend_limits.py @@ -0,0 +1,803 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, overload + +import httpx + +from ..types import SpendLimitPeriod, spend_limit_create_params, spend_limit_delete_params, spend_limit_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, required_args, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.spend_limit_period import SpendLimitPeriod +from ..types.spend_limit_response import SpendLimitResponse +from ..types.spend_limit_list_response import SpendLimitListResponse + +__all__ = ["SpendLimitsResource", "AsyncSpendLimitsResource"] + + +class SpendLimitsResource(SyncAPIResource): + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + + @cached_property + def with_raw_response(self) -> SpendLimitsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#accessing-raw-response-data-eg-headers + """ + return SpendLimitsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SpendLimitsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#with_streaming_response + """ + return SpendLimitsResourceWithStreamingResponse(self) + + @overload + def create( + self, + *, + amount: float, + product: str, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Sets a limit for a product and period that has none. + + Send exactly one of + `amount` and `unlimited: true`. The period's spend is checked at once: if it is + already above the new limit, the product is blocked immediately + (`evaluation.blocked_now`). Returns 409 when a limit already exists for the + product and period; update it instead. + + Args: + amount: Limit in USD. `0` blocks at the first cent of spend. + + product: Product to limit, as returned in `product` by the list operation. + + period: `daily` is the current UTC day; `monthly` is the current UTC calendar month. + + reason: Why the limit is set or changed, kept for audit. + + unlimited: Optional; only `false` is allowed together with `amount`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + product: str, + unlimited: Literal[True], + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Sets a limit for a product and period that has none. + + Send exactly one of + `amount` and `unlimited: true`. The period's spend is checked at once: if it is + already above the new limit, the product is blocked immediately + (`evaluation.blocked_now`). Returns 409 when a limit already exists for the + product and period; update it instead. + + Args: + product: Product to limit, as returned in `product` by the list operation. + + unlimited: `true`: explicitly no cap. + + period: `daily` is the current UTC day; `monthly` is the current UTC calendar month. + + reason: Why the limit is set or changed, kept for audit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["amount", "product"], ["product", "unlimited"]) + def create( + self, + *, + amount: float | Omit = omit, + product: str, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Literal[True] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + return self._post( + "/spend_limits", + body=maybe_transform( + { + "amount": amount, + "product": product, + "period": period, + "reason": reason, + "unlimited": unlimited, + }, + spend_limit_create_params.SpendLimitCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SpendLimitResponse, + ) + + @overload + def update( + self, + product: str, + *, + amount: float, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Replaces the value of the existing limit for the product and period. + + Send + exactly one of `amount` and `unlimited: true`. The period's spend is checked at + once: raising the limit above the spend lifts the period's block + (`evaluation.released`), and lowering it below the spend blocks the product + (`evaluation.blocked_now`). Returns 404 when no limit is set; create it instead. + + Args: + amount: Limit in USD. `0` blocks at the first cent of spend. + + period: Limit period. Defaults to `daily`; send it explicitly. + + reason: Why the limit is set or changed, kept for audit. + + unlimited: Optional; only `false` is allowed together with `amount`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def update( + self, + product: str, + *, + unlimited: Literal[True], + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Replaces the value of the existing limit for the product and period. + + Send + exactly one of `amount` and `unlimited: true`. The period's spend is checked at + once: raising the limit above the spend lifts the period's block + (`evaluation.released`), and lowering it below the spend blocks the product + (`evaluation.blocked_now`). Returns 404 when no limit is set; create it instead. + + Args: + unlimited: `true`: explicitly no cap. + + period: Limit period. Defaults to `daily`; send it explicitly. + + reason: Why the limit is set or changed, kept for audit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["amount"], ["unlimited"]) + def update( + self, + product: str, + *, + amount: float | Omit = omit, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Literal[True] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + if not product: + raise ValueError(f"Expected a non-empty value for `product` but received {product!r}") + return self._patch( + path_template("/spend_limits/{product}", product=product), + body=maybe_transform( + { + "amount": amount, + "reason": reason, + "unlimited": unlimited, + }, + spend_limit_update_params.SpendLimitUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"period": period}, spend_limit_update_params.SpendLimitUpdateParams), + ), + cast_to=SpendLimitResponse, + ) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitListResponse: + """ + Returns one entry per product and period you can set a limit on, with the limit, + the spend so far in the period and whether the product is blocked. An entry + without a limit is still listed (`limit: null`). When the spend cannot be read, + the entry is returned with `spend_usd: null` and `spend_error` set. The list is + not paginated. + """ + return self._get( + "/spend_limits", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SpendLimitListResponse, + ) + + def delete( + self, + product: str, + *, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Removes the limit for the product and period. + + For `inference`, which has no + default limit, the product becomes unlimited for the period and the period's + block is lifted (`evaluation.released`). The response carries `limit: null` and + the `effective_limit_usd` that applies after the removal. Returns 404 when no + limit is set. + + Args: + period: Limit period. Defaults to `daily`; send it explicitly. + + reason: Why the limit is removed, kept for audit. At most 500 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not product: + raise ValueError(f"Expected a non-empty value for `product` but received {product!r}") + return self._delete( + path_template("/spend_limits/{product}", product=product), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "period": period, + "reason": reason, + }, + spend_limit_delete_params.SpendLimitDeleteParams, + ), + ), + cast_to=SpendLimitResponse, + ) + + +class AsyncSpendLimitsResource(AsyncAPIResource): + """Daily and monthly spend limits per product. + + A limit applies to the organization of the authenticated user, or to the user's own account when they belong to no organization; every user of the organization sees and changes the same limits. + + - **Periods.** `daily` covers the current UTC day and `monthly` the current UTC calendar month. The two limits are independent: you can set either, both or neither. + - **Blocking.** When spend in a period goes above the limit (strictly greater), the product is blocked until the period ends: 00:00 UTC the next day for `daily`, 00:00 UTC on the 1st of the next month for `monthly`. A block appears within about 2 minutes (daily) or 10 minutes (monthly) of the spend being recorded. + - **Changes apply immediately.** Creating, updating or deleting a limit checks the period's spend in the same request: raising the limit above the spend, or removing it, lifts that period's block, and lowering it below the spend blocks the product at once. The `evaluation` object in the response says what happened. + - **Supported products.** Today only `inference` supports spend limits. A blocked account gets HTTP 403 with the error title `Inference spend limit reached` (code `10039`) on new billable chat completions, Responses, Anthropic Messages and classification requests; requests already running finish normally. Take the list of products from the list operation. + - **Limits set by Telnyx.** Telnyx support can also set a limit on your account. It is listed with `origin: operator` and you can update or delete it like your own. + """ + + @cached_property + def with_raw_response(self) -> AsyncSpendLimitsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#accessing-raw-response-data-eg-headers + """ + return AsyncSpendLimitsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSpendLimitsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/team-telnyx/telnyx-python#with_streaming_response + """ + return AsyncSpendLimitsResourceWithStreamingResponse(self) + + @overload + async def create( + self, + *, + amount: float, + product: str, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Sets a limit for a product and period that has none. + + Send exactly one of + `amount` and `unlimited: true`. The period's spend is checked at once: if it is + already above the new limit, the product is blocked immediately + (`evaluation.blocked_now`). Returns 409 when a limit already exists for the + product and period; update it instead. + + Args: + amount: Limit in USD. `0` blocks at the first cent of spend. + + product: Product to limit, as returned in `product` by the list operation. + + period: `daily` is the current UTC day; `monthly` is the current UTC calendar month. + + reason: Why the limit is set or changed, kept for audit. + + unlimited: Optional; only `false` is allowed together with `amount`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + product: str, + unlimited: Literal[True], + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Sets a limit for a product and period that has none. + + Send exactly one of + `amount` and `unlimited: true`. The period's spend is checked at once: if it is + already above the new limit, the product is blocked immediately + (`evaluation.blocked_now`). Returns 409 when a limit already exists for the + product and period; update it instead. + + Args: + product: Product to limit, as returned in `product` by the list operation. + + unlimited: `true`: explicitly no cap. + + period: `daily` is the current UTC day; `monthly` is the current UTC calendar month. + + reason: Why the limit is set or changed, kept for audit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["amount", "product"], ["product", "unlimited"]) + async def create( + self, + *, + amount: float | Omit = omit, + product: str, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Literal[True] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + return await self._post( + "/spend_limits", + body=await async_maybe_transform( + { + "amount": amount, + "product": product, + "period": period, + "reason": reason, + "unlimited": unlimited, + }, + spend_limit_create_params.SpendLimitCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SpendLimitResponse, + ) + + @overload + async def update( + self, + product: str, + *, + amount: float, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Replaces the value of the existing limit for the product and period. + + Send + exactly one of `amount` and `unlimited: true`. The period's spend is checked at + once: raising the limit above the spend lifts the period's block + (`evaluation.released`), and lowering it below the spend blocks the product + (`evaluation.blocked_now`). Returns 404 when no limit is set; create it instead. + + Args: + amount: Limit in USD. `0` blocks at the first cent of spend. + + period: Limit period. Defaults to `daily`; send it explicitly. + + reason: Why the limit is set or changed, kept for audit. + + unlimited: Optional; only `false` is allowed together with `amount`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def update( + self, + product: str, + *, + unlimited: Literal[True], + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Replaces the value of the existing limit for the product and period. + + Send + exactly one of `amount` and `unlimited: true`. The period's spend is checked at + once: raising the limit above the spend lifts the period's block + (`evaluation.released`), and lowering it below the spend blocks the product + (`evaluation.blocked_now`). Returns 404 when no limit is set; create it instead. + + Args: + unlimited: `true`: explicitly no cap. + + period: Limit period. Defaults to `daily`; send it explicitly. + + reason: Why the limit is set or changed, kept for audit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["amount"], ["unlimited"]) + async def update( + self, + product: str, + *, + amount: float | Omit = omit, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + unlimited: Literal[False] | Literal[True] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + if not product: + raise ValueError(f"Expected a non-empty value for `product` but received {product!r}") + return await self._patch( + path_template("/spend_limits/{product}", product=product), + body=await async_maybe_transform( + { + "amount": amount, + "reason": reason, + "unlimited": unlimited, + }, + spend_limit_update_params.SpendLimitUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"period": period}, spend_limit_update_params.SpendLimitUpdateParams), + ), + cast_to=SpendLimitResponse, + ) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitListResponse: + """ + Returns one entry per product and period you can set a limit on, with the limit, + the spend so far in the period and whether the product is blocked. An entry + without a limit is still listed (`limit: null`). When the spend cannot be read, + the entry is returned with `spend_usd: null` and `spend_error` set. The list is + not paginated. + """ + return await self._get( + "/spend_limits", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SpendLimitListResponse, + ) + + async def delete( + self, + product: str, + *, + period: SpendLimitPeriod | Omit = omit, + reason: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SpendLimitResponse: + """Removes the limit for the product and period. + + For `inference`, which has no + default limit, the product becomes unlimited for the period and the period's + block is lifted (`evaluation.released`). The response carries `limit: null` and + the `effective_limit_usd` that applies after the removal. Returns 404 when no + limit is set. + + Args: + period: Limit period. Defaults to `daily`; send it explicitly. + + reason: Why the limit is removed, kept for audit. At most 500 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not product: + raise ValueError(f"Expected a non-empty value for `product` but received {product!r}") + return await self._delete( + path_template("/spend_limits/{product}", product=product), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "period": period, + "reason": reason, + }, + spend_limit_delete_params.SpendLimitDeleteParams, + ), + ), + cast_to=SpendLimitResponse, + ) + + +class SpendLimitsResourceWithRawResponse: + def __init__(self, spend_limits: SpendLimitsResource) -> None: + self._spend_limits = spend_limits + + self.create = to_raw_response_wrapper( + spend_limits.create, + ) + self.update = to_raw_response_wrapper( + spend_limits.update, + ) + self.list = to_raw_response_wrapper( + spend_limits.list, + ) + self.delete = to_raw_response_wrapper( + spend_limits.delete, + ) + + +class AsyncSpendLimitsResourceWithRawResponse: + def __init__(self, spend_limits: AsyncSpendLimitsResource) -> None: + self._spend_limits = spend_limits + + self.create = async_to_raw_response_wrapper( + spend_limits.create, + ) + self.update = async_to_raw_response_wrapper( + spend_limits.update, + ) + self.list = async_to_raw_response_wrapper( + spend_limits.list, + ) + self.delete = async_to_raw_response_wrapper( + spend_limits.delete, + ) + + +class SpendLimitsResourceWithStreamingResponse: + def __init__(self, spend_limits: SpendLimitsResource) -> None: + self._spend_limits = spend_limits + + self.create = to_streamed_response_wrapper( + spend_limits.create, + ) + self.update = to_streamed_response_wrapper( + spend_limits.update, + ) + self.list = to_streamed_response_wrapper( + spend_limits.list, + ) + self.delete = to_streamed_response_wrapper( + spend_limits.delete, + ) + + +class AsyncSpendLimitsResourceWithStreamingResponse: + def __init__(self, spend_limits: AsyncSpendLimitsResource) -> None: + self._spend_limits = spend_limits + + self.create = async_to_streamed_response_wrapper( + spend_limits.create, + ) + self.update = async_to_streamed_response_wrapper( + spend_limits.update, + ) + self.list = async_to_streamed_response_wrapper( + spend_limits.list, + ) + self.delete = async_to_streamed_response_wrapper( + spend_limits.delete, + ) diff --git a/src/telnyx/types/__init__.py b/src/telnyx/types/__init__.py index 81a32b344..694746e9f 100644 --- a/src/telnyx/types/__init__.py +++ b/src/telnyx/types/__init__.py @@ -82,6 +82,7 @@ from .offset_meta import OffsetMeta as OffsetMeta from .outbound_ip import OutboundIP as OutboundIP from .rcs_to_item import RcsToItem as RcsToItem +from .spend_limit import SpendLimit as SpendLimit from .uac_inbound import UacInbound as UacInbound from .call_bridged import CallBridged as CallBridged from .email_domain import EmailDomain as EmailDomain @@ -194,6 +195,7 @@ from .call_speak_started import CallSpeakStarted as CallSpeakStarted from .conference_created import ConferenceCreated as ConferenceCreated from .credential_inbound import CredentialInbound as CredentialInbound +from .dir_bpo_loa_params import DirBpoLoaParams as DirBpoLoaParams from .dir_new_loa_params import DirNewLoaParams as DirNewLoaParams from .doc_service_record import DocServiceRecord as DocServiceRecord from .email_dmarc_policy import EmailDmarcPolicy as EmailDmarcPolicy @@ -214,6 +216,7 @@ from .recording_response import RecordingResponse as RecordingResponse from .room_create_params import RoomCreateParams as RoomCreateParams from .room_update_params import RoomUpdateParams as RoomUpdateParams +from .spend_limit_period import SpendLimitPeriod as SpendLimitPeriod from .transport_protocol import TransportProtocol as TransportProtocol from .uac_outbound_param import UacOutboundParam as UacOutboundParam from .video_region_param import VideoRegionParam as VideoRegionParam @@ -225,6 +228,7 @@ from .call_siprec_stopped import CallSiprecStopped as CallSiprecStopped from .comment_list_params import CommentListParams as CommentListParams from .credential_outbound import CredentialOutbound as CredentialOutbound +from .dir_delete_response import DirDeleteResponse as DirDeleteResponse from .email_domain_status import EmailDomainStatus as EmailDomainStatus from .external_connection import ExternalConnection as ExternalConnection from .fax_create_response import FaxCreateResponse as FaxCreateResponse @@ -276,6 +280,7 @@ from .room_retrieve_params import RoomRetrieveParams as RoomRetrieveParams from .room_update_response import RoomUpdateResponse as RoomUpdateResponse from .sim_card_list_params import SimCardListParams as SimCardListParams +from .spend_limit_response import SpendLimitResponse as SpendLimitResponse from .suppressed_recipient import SuppressedRecipient as SuppressedRecipient from .telephony_credential import TelephonyCredential as TelephonyCredential from .texml_secrets_params import TexmlSecretsParams as TexmlSecretsParams @@ -397,6 +402,7 @@ from .report_list_mdrs_params import ReportListMdrsParams as ReportListMdrsParams from .report_list_wdrs_params import ReportListWdrsParams as ReportListWdrsParams from .requirement_list_params import RequirementListParams as RequirementListParams +from .signature_payload_param import SignaturePayloadParam as SignaturePayloadParam from .tracking_settings_param import TrackingSettingsParam as TrackingSettingsParam from .transcribe_client_event import TranscribeClientEvent as TranscribeClientEvent from .transcribe_server_event import TranscribeServerEvent as TranscribeServerEvent @@ -484,6 +490,10 @@ from .report_list_wdrs_response import ReportListWdrsResponse as ReportListWdrsResponse from .siprec_connector_response import SiprecConnectorResponse as SiprecConnectorResponse from .sound_modifications_param import SoundModificationsParam as SoundModificationsParam +from .spend_limit_create_params import SpendLimitCreateParams as SpendLimitCreateParams +from .spend_limit_delete_params import SpendLimitDeleteParams as SpendLimitDeleteParams +from .spend_limit_list_response import SpendLimitListResponse as SpendLimitListResponse +from .spend_limit_update_params import SpendLimitUpdateParams as SpendLimitUpdateParams from .stream_bidirectional_mode import StreamBidirectionalMode as StreamBidirectionalMode from .stream_client_event_param import StreamClientEventParam as StreamClientEventParam from .uac_inbound_request_param import UacInboundRequestParam as UacInboundRequestParam @@ -636,6 +646,7 @@ from .billing_group_delete_response import BillingGroupDeleteResponse as BillingGroupDeleteResponse from .billing_group_update_response import BillingGroupUpdateResponse as BillingGroupUpdateResponse from .bot_challenge_create_response import BotChallengeCreateResponse as BotChallengeCreateResponse +from .bpo_authorization_input_param import BpoAuthorizationInputParam as BpoAuthorizationInputParam from .bulk_sim_card_action_detailed import BulkSimCardActionDetailed as BulkSimCardActionDetailed from .call_left_queue_webhook_event import CallLeftQueueWebhookEvent as CallLeftQueueWebhookEvent from .call_reason_validate_response import CallReasonValidateResponse as CallReasonValidateResponse @@ -1132,6 +1143,9 @@ from .conference_speak_started_webhook_event import ( ConferenceSpeakStartedWebhookEvent as ConferenceSpeakStartedWebhookEvent, ) +from .dir_retrieve_bpo_authorizations_params import ( + DirRetrieveBpoAuthorizationsParams as DirRetrieveBpoAuthorizationsParams, +) from .dynamic_emergency_endpoint_list_params import ( DynamicEmergencyEndpointListParams as DynamicEmergencyEndpointListParams, ) @@ -1318,6 +1332,9 @@ from .conversation_relay_embedded_config_param import ( ConversationRelayEmbeddedConfigParam as ConversationRelayEmbeddedConfigParam, ) +from .dir_retrieve_bpo_authorizations_response import ( + DirRetrieveBpoAuthorizationsResponse as DirRetrieveBpoAuthorizationsResponse, +) from .document_generate_download_link_response import ( DocumentGenerateDownloadLinkResponse as DocumentGenerateDownloadLinkResponse, ) diff --git a/src/telnyx/types/ai/__init__.py b/src/telnyx/types/ai/__init__.py index f532cdc1b..03e9b436a 100644 --- a/src/telnyx/types/ai/__init__.py +++ b/src/telnyx/types/ai/__init__.py @@ -12,6 +12,7 @@ from .enabled_features import EnabledFeatures as EnabledFeatures from .tool_list_params import ToolListParams as ToolListParams from .hangup_tool_param import HangupToolParam as HangupToolParam +from .external_llm_param import ExternalLlmParam as ExternalLlmParam from .prompt_sync_status import PromptSyncStatus as PromptSyncStatus from .tool_create_params import ToolCreateParams as ToolCreateParams from .tool_update_params import ToolUpdateParams as ToolUpdateParams @@ -56,8 +57,11 @@ from .mcp_server_update_params import McpServerUpdateParams as McpServerUpdateParams from .messaging_settings_param import MessagingSettingsParam as MessagingSettingsParam from .telephony_settings_param import TelephonySettingsParam as TelephonySettingsParam +from .websocket_settings_param import WebsocketSettingsParam as WebsocketSettingsParam from .assistant_retrieve_params import AssistantRetrieveParams as AssistantRetrieveParams from .assistant_send_sms_params import AssistantSendSMSParams as AssistantSendSMSParams +from .assistant_whatsapp_params import AssistantWhatsappParams as AssistantWhatsappParams +from .delegation_settings_param import DelegationSettingsParam as DelegationSettingsParam from .fallback_config_req_param import FallbackConfigReqParam as FallbackConfigReqParam from .start_speaking_plan_param import StartSpeakingPlanParam as StartSpeakingPlanParam from .assistant_a2_a_agent_param import AssistantA2AAgentParam as AssistantA2AAgentParam @@ -121,7 +125,9 @@ from .hangup_tool_params import HangupToolParams as HangupToolParams from .messaging_settings import MessagingSettings as MessagingSettings from .telephony_settings import TelephonySettings as TelephonySettings + from .websocket_settings import WebsocketSettings as WebsocketSettings from .collection_envelope import CollectionEnvelope as CollectionEnvelope + from .delegation_settings import DelegationSettings as DelegationSettings from .inference_embedding import InferenceEmbedding as InferenceEmbedding from .start_speaking_plan import StartSpeakingPlan as StartSpeakingPlan from .assistant_a2_a_agent import AssistantA2AAgent as AssistantA2AAgent @@ -142,6 +148,7 @@ from .conversation_list_response import ConversationListResponse as ConversationListResponse from .post_conversation_settings import PostConversationSettings as PostConversationSettings from .assistant_send_sms_response import AssistantSendSMSResponse as AssistantSendSMSResponse + from .assistant_whatsapp_response import AssistantWhatsappResponse as AssistantWhatsappResponse from .embedding_retrieve_response import EmbeddingRetrieveResponse as EmbeddingRetrieveResponse from .conversation_update_response import ConversationUpdateResponse as ConversationUpdateResponse from .transcription_settings_config import TranscriptionSettingsConfig as TranscriptionSettingsConfig @@ -191,6 +198,10 @@ def __getattr__(name: str) -> Any: from .conversation_flow import ConversationFlow return ConversationFlow + if name == "DelegationSettings": + from .delegation_settings import DelegationSettings + + return DelegationSettings if name == "ExternalLlm": from .external_llm import ExternalLlm @@ -287,6 +298,10 @@ def __getattr__(name: str) -> Any: from .transcription_settings_config import TranscriptionSettingsConfig return TranscriptionSettingsConfig + if name == "WebsocketSettings": + from .websocket_settings import WebsocketSettings + + return WebsocketSettings if name == "WidgetSettings": from .widget_settings import WidgetSettings @@ -303,6 +318,10 @@ def __getattr__(name: str) -> Any: from .assistant_send_sms_response import AssistantSendSMSResponse return AssistantSendSMSResponse + if name == "AssistantWhatsappResponse": + from .assistant_whatsapp_response import AssistantWhatsappResponse + + return AssistantWhatsappResponse if name == "AudioTranscriptionResponseWord": from .audio_transcription_response_word import AudioTranscriptionResponseWord diff --git a/src/telnyx/types/ai/assistant_create_params.py b/src/telnyx/types/ai/assistant_create_params.py index 3e8aee566..8c719203a 100644 --- a/src/telnyx/types/ai/assistant_create_params.py +++ b/src/telnyx/types/ai/assistant_create_params.py @@ -16,6 +16,8 @@ from .observability_req_param import ObservabilityReqParam from .messaging_settings_param import MessagingSettingsParam from .telephony_settings_param import TelephonySettingsParam +from .websocket_settings_param import WebsocketSettingsParam +from .delegation_settings_param import DelegationSettingsParam from .fallback_config_req_param import FallbackConfigReqParam from .assistant_a2_a_agent_param import AssistantA2AAgentParam from .assistant_mcp_server_param import AssistantMcpServerParam @@ -60,6 +62,17 @@ class AssistantCreateParams(TypedDict, total=False): every edge's endpoints reference real nodes. """ + delegation_settings: DelegationSettingsParam + """ + Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + """ + description: str dynamic_variables: Dict[str, object] @@ -188,6 +201,15 @@ class AssistantCreateParams(TypedDict, total=False): voice_settings: InferenceEmbeddingVoiceSettingsParam + websocket_settings: WebsocketSettingsParam + """ + Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + """ + widget_settings: WidgetSettingsParam """Configuration settings for the assistant's web widget.""" diff --git a/src/telnyx/types/ai/assistant_update_params.py b/src/telnyx/types/ai/assistant_update_params.py index 4032cc72a..52bea6f3d 100644 --- a/src/telnyx/types/ai/assistant_update_params.py +++ b/src/telnyx/types/ai/assistant_update_params.py @@ -15,6 +15,8 @@ from .observability_req_param import ObservabilityReqParam from .messaging_settings_param import MessagingSettingsParam from .telephony_settings_param import TelephonySettingsParam +from .websocket_settings_param import WebsocketSettingsParam +from .delegation_settings_param import DelegationSettingsParam from .fallback_config_req_param import FallbackConfigReqParam from .assistant_a2_a_agent_param import AssistantA2AAgentParam from .assistant_mcp_server_param import AssistantMcpServerParam @@ -51,6 +53,17 @@ class AssistantUpdateParams(TypedDict, total=False): every edge's endpoints reference real nodes. """ + delegation_settings: DelegationSettingsParam + """ + Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + """ + description: str dynamic_variables: Dict[str, object] @@ -208,5 +221,14 @@ class AssistantUpdateParams(TypedDict, total=False): voice_settings: InferenceEmbeddingVoiceSettingsParam + websocket_settings: WebsocketSettingsParam + """ + Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + """ + widget_settings: WidgetSettingsParam """Configuration settings for the assistant's web widget.""" diff --git a/src/telnyx/types/ai/assistant_whatsapp_params.py b/src/telnyx/types/ai/assistant_whatsapp_params.py new file mode 100644 index 000000000..dab5c4502 --- /dev/null +++ b/src/telnyx/types/ai/assistant_whatsapp_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["AssistantWhatsappParams"] + + +class AssistantWhatsappParams(TypedDict, total=False): + content: Required[str] + """ + Instruction for the assistant, including the values for the template variables, + e.g. `Send the login verification code 482913 to the customer.` + """ + + from_: Required[Annotated[str, PropertyInfo(alias="from")]] + """WhatsApp number on your account to send from, in E.164 format. + + Its messaging profile must have this assistant configured. + """ + + to: Required[str] + """ + Customer to message, as an E.164 phone number or a WhatsApp business-scoped user + ID (BSUID). + """ + + conversation_metadata: Dict[str, Union[str, int, bool]] + """Metadata stored on the conversation. + + Keys starting with `telnyx_` and the `assistant_id` key are reserved. + """ + + idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] diff --git a/src/telnyx/types/ai/assistant_whatsapp_response.py b/src/telnyx/types/ai/assistant_whatsapp_response.py new file mode 100644 index 000000000..5eab9f3ba --- /dev/null +++ b/src/telnyx/types/ai/assistant_whatsapp_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._models import BaseModel + +__all__ = ["AssistantWhatsappResponse"] + + +class AssistantWhatsappResponse(BaseModel): + conversation_id: str + """ID of the conversation created for this WhatsApp chat.""" + + message_id: str + """ID of the WhatsApp template message that was sent.""" diff --git a/src/telnyx/types/ai/assistants/version_update_params.py b/src/telnyx/types/ai/assistants/version_update_params.py index e01eff95a..7d7c5a0b2 100644 --- a/src/telnyx/types/ai/assistants/version_update_params.py +++ b/src/telnyx/types/ai/assistants/version_update_params.py @@ -15,6 +15,8 @@ from ..observability_req_param import ObservabilityReqParam from ..messaging_settings_param import MessagingSettingsParam from ..telephony_settings_param import TelephonySettingsParam +from ..websocket_settings_param import WebsocketSettingsParam +from ..delegation_settings_param import DelegationSettingsParam from ..fallback_config_req_param import FallbackConfigReqParam from ..assistant_a2_a_agent_param import AssistantA2AAgentParam from ..assistant_mcp_server_param import AssistantMcpServerParam @@ -53,6 +55,17 @@ class VersionUpdateParams(TypedDict, total=False): every edge's endpoints reference real nodes. """ + delegation_settings: DelegationSettingsParam + """ + Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + """ + description: str dynamic_variables: Dict[str, object] @@ -204,5 +217,14 @@ class VersionUpdateParams(TypedDict, total=False): voice_settings: InferenceEmbeddingVoiceSettingsParam + websocket_settings: WebsocketSettingsParam + """ + Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + """ + widget_settings: WidgetSettingsParam """Configuration settings for the assistant's web widget.""" diff --git a/src/telnyx/types/ai/conversation_flow_req_param.py b/src/telnyx/types/ai/conversation_flow_req_param.py index 5bc9ae746..bf9e238f5 100644 --- a/src/telnyx/types/ai/conversation_flow_req_param.py +++ b/src/telnyx/types/ai/conversation_flow_req_param.py @@ -26,8 +26,8 @@ class ConversationFlowReqParam(TypedDict, total=False): nodes: Required[Iterable[Node]] """All nodes in the flow. - Must contain `start_node_id`. Each node is a prompt node (`type: prompt`) or a - tool node (`type: tool`). + Must contain `start_node_id`. Each node is a prompt node (`type: prompt`), a + tool node (`type: tool`), or a speak node (`type: speak`). """ start_node_id: Required[str] diff --git a/src/telnyx/types/ai/delegation_settings.py b/src/telnyx/types/ai/delegation_settings.py new file mode 100644 index 000000000..cf0fcbc53 --- /dev/null +++ b/src/telnyx/types/ai/delegation_settings.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .external_llm import ExternalLlm + +__all__ = ["DelegationSettings"] + + +class DelegationSettings(BaseModel): + """ + Splits the conversation between a frontend model that talks to the caller and a backend model that does the work. On the GPT-Live route the frontend model cannot call tools at all — when it needs something done it raises a delegation and waits. On the chat completion route the frontend keeps a single `delegate` tool that returns immediately, so the conversation carries on while the backend works. Either way the backend's answer is spoken as commentary or kept as silent context, depending on `speak_results`. Beta feature. + """ + + enabled: Optional[bool] = None + """Whether the assistant delegates work to a backend model. + + Defaults to `true`: a GPT-Live assistant with delegation disabled can hold a + conversation but can never look anything up or run a tool. + """ + + external_llm: Optional[ExternalLlm] = None + """ + Run the backend on your own OpenAI-compatible endpoint instead of a + Telnyx-hosted model. As above, a raw `api_key` here is rejected — reference an + integration secret with `external_llm.llm_api_key_ref` instead. + """ + + instructions: Optional[str] = None + """Extra instructions for the backend model, in addition to the assistant's own. + + Use this for the business rules the backend needs and the talking model does + not. + """ + + llm_api_key_ref: Optional[str] = None + """Integration secret identifier for the backend model's API key. + + Required for models from providers other than Telnyx, OpenAI and Anthropic. A + raw `api_key` is rejected rather than ignored, so that no plaintext credential + is stored on the assistant. + """ + + mode: Optional[Literal["telnyx", "client"]] = None + """Who answers a delegation. + + `telnyx` runs the backend model on Telnyx with the assistant's own tools, MCP + servers and observability. `client` relays the delegation to a server you host + over the WebSocket configured in `websocket_settings`: Telnyx sends a + `session.delegation.created` frame and waits for your + `session.delegation.completed` answer. That answer is text only, since the + socket offers no tool vocabulary. If no socket is connected the delegation is + refused and the assistant tells the caller it cannot look things up right now. + Defaults to `telnyx`. + """ + + model: Optional[str] = None + """The backend model that answers delegations. + + Must be a model available for AI Assistants. When enabling `telnyx` delegation, + explicitly set this field or `external_llm.model`; a configuration without + either backend model is rejected. Only applies when `mode` is `telnyx`. + """ + + speak_results: Optional[bool] = None + """Whether the backend's answer is spoken to the caller. + + When `true` the result is appended as commentary and paraphrased aloud; when + `false` it is kept as silent context that informs later answers without being + read out. Defaults to `true`. + """ diff --git a/src/telnyx/types/ai/delegation_settings_param.py b/src/telnyx/types/ai/delegation_settings_param.py new file mode 100644 index 000000000..fd9b8a24d --- /dev/null +++ b/src/telnyx/types/ai/delegation_settings_param.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +from .external_llm_param import ExternalLlmParam + +__all__ = ["DelegationSettingsParam"] + + +class DelegationSettingsParam(TypedDict, total=False): + """ + Splits the conversation between a frontend model that talks to the caller and a backend model that does the work. On the GPT-Live route the frontend model cannot call tools at all — when it needs something done it raises a delegation and waits. On the chat completion route the frontend keeps a single `delegate` tool that returns immediately, so the conversation carries on while the backend works. Either way the backend's answer is spoken as commentary or kept as silent context, depending on `speak_results`. Beta feature. + """ + + enabled: bool + """Whether the assistant delegates work to a backend model. + + Defaults to `true`: a GPT-Live assistant with delegation disabled can hold a + conversation but can never look anything up or run a tool. + """ + + external_llm: ExternalLlmParam + """ + Run the backend on your own OpenAI-compatible endpoint instead of a + Telnyx-hosted model. As above, a raw `api_key` here is rejected — reference an + integration secret with `external_llm.llm_api_key_ref` instead. + """ + + instructions: str + """Extra instructions for the backend model, in addition to the assistant's own. + + Use this for the business rules the backend needs and the talking model does + not. + """ + + llm_api_key_ref: str + """Integration secret identifier for the backend model's API key. + + Required for models from providers other than Telnyx, OpenAI and Anthropic. A + raw `api_key` is rejected rather than ignored, so that no plaintext credential + is stored on the assistant. + """ + + mode: Literal["telnyx", "client"] + """Who answers a delegation. + + `telnyx` runs the backend model on Telnyx with the assistant's own tools, MCP + servers and observability. `client` relays the delegation to a server you host + over the WebSocket configured in `websocket_settings`: Telnyx sends a + `session.delegation.created` frame and waits for your + `session.delegation.completed` answer. That answer is text only, since the + socket offers no tool vocabulary. If no socket is connected the delegation is + refused and the assistant tells the caller it cannot look things up right now. + Defaults to `telnyx`. + """ + + model: str + """The backend model that answers delegations. + + Must be a model available for AI Assistants. When enabling `telnyx` delegation, + explicitly set this field or `external_llm.model`; a configuration without + either backend model is rejected. Only applies when `mode` is `telnyx`. + """ + + speak_results: bool + """Whether the backend's answer is spoken to the caller. + + When `true` the result is appended as commentary and paraphrased aloud; when + `false` it is kept as silent context that informs later answers without being + read out. Defaults to `true`. + """ diff --git a/src/telnyx/types/ai/external_llm_param.py b/src/telnyx/types/ai/external_llm_param.py new file mode 100644 index 000000000..e0e04abfb --- /dev/null +++ b/src/telnyx/types/ai/external_llm_param.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .authentication_method import AuthenticationMethod + +__all__ = ["ExternalLlmParam"] + + +class ExternalLlmParam(TypedDict, total=False): + base_url: Required[str] + """Base URL for the external LLM endpoint.""" + + model: Required[str] + """Model identifier to use with the external LLM endpoint.""" + + authentication_method: AuthenticationMethod + """Authentication method used when connecting to the external LLM endpoint.""" + + certificate_ref: str + """ + Integration secret identifier for the client certificate used with certificate + authentication. + """ + + forward_metadata: bool + """ + When `true`, Telnyx forwards the assistant's dynamic variables to the external + LLM endpoint as a top-level `extra_metadata` object on the chat completion + request body. Defaults to `false`. Example payload sent to the external + endpoint: + `{"extra_metadata": {"customer_name": "Jane", "account_id": "acct_789", "telnyx_agent_target": "+13125550100", "telnyx_end_user_target": "+13125550123"}}`. + Distinct from OpenAI's native `metadata` field, which has its own size and type + limits. + """ + + llm_api_key_ref: str + """Integration secret identifier for the external LLM API key.""" + + token_retrieval_url: str + """ + URL used to retrieve an access token when certificate authentication is enabled. + """ diff --git a/src/telnyx/types/ai/flow_edge.py b/src/telnyx/types/ai/flow_edge.py index 9dc819e1f..83553f489 100644 --- a/src/telnyx/types/ai/flow_edge.py +++ b/src/telnyx/types/ai/flow_edge.py @@ -22,15 +22,26 @@ class ConditionLlmCondition(BaseModel): - """Edge condition evaluated by the LLM from a natural-language prompt. - - The model is asked to judge the prompt against conversation context and - returns true/false. Use this for fuzzy intents that aren't expressible as - a deterministic expression (e.g. 'user wants to escalate to a human'). + """Edge condition routed by the assistant's LLM from a natural-language + prompt. + + How the edge is decided depends on the channel. On calls, each outgoing + `llm` condition is offered to the assistant's model as a transition tool + alongside the assistant's tools, and the edge fires when the model + selects it; the platform does not evaluate the prompt itself, and + instructions that forbid or discourage tool calls can stop these edges + from firing. On chat channels, the edge prompts are evaluated in a + separate model call after the reply, which does not use the assistant's + instructions. Use this for fuzzy intents that aren't expressible as a + deterministic expression (e.g. 'user wants to escalate to a human'). """ prompt: str - """Natural-language criterion the LLM judges as true/false.""" + """Natural-language criterion the model routes on. + + On calls this is offered to the model as the transition tool's description; on + chat channels it is judged as a statement in the post-reply evaluation call. + """ type: Literal["llm"] @@ -129,8 +140,14 @@ class FlowEdge(BaseModel): The target is either another node in the same flow (`NodeTarget`) or a different assistant (`AssistantTarget`). Multiple edges may share a - `start_node_id`; the runtime evaluates them in the order they're - declared and takes the first whose condition is true. + `start_node_id`. On calls, `expression` conditions are evaluated before + the model turn and take precedence over `llm` conditions regardless of + declaration order, while `llm` conditions are offered to the assistant's + model as transition tools and fire when the model selects one. On chat + channels, an `expression` condition that is true when the turn begins + routes before the reply is generated; all conditioned edges that remain + are considered together in declaration order after the reply, and the + first true one wins. """ id: str diff --git a/src/telnyx/types/ai/flow_edge_param.py b/src/telnyx/types/ai/flow_edge_param.py index c91ba818f..e60fcce19 100644 --- a/src/telnyx/types/ai/flow_edge_param.py +++ b/src/telnyx/types/ai/flow_edge_param.py @@ -20,15 +20,26 @@ class ConditionLlmCondition(TypedDict, total=False): - """Edge condition evaluated by the LLM from a natural-language prompt. - - The model is asked to judge the prompt against conversation context and - returns true/false. Use this for fuzzy intents that aren't expressible as - a deterministic expression (e.g. 'user wants to escalate to a human'). + """Edge condition routed by the assistant's LLM from a natural-language + prompt. + + How the edge is decided depends on the channel. On calls, each outgoing + `llm` condition is offered to the assistant's model as a transition tool + alongside the assistant's tools, and the edge fires when the model + selects it; the platform does not evaluate the prompt itself, and + instructions that forbid or discourage tool calls can stop these edges + from firing. On chat channels, the edge prompts are evaluated in a + separate model call after the reply, which does not use the assistant's + instructions. Use this for fuzzy intents that aren't expressible as a + deterministic expression (e.g. 'user wants to escalate to a human'). """ prompt: Required[str] - """Natural-language criterion the LLM judges as true/false.""" + """Natural-language criterion the model routes on. + + On calls this is offered to the model as the transition tool's description; on + chat channels it is judged as a statement in the post-reply evaluation call. + """ type: Required[Literal["llm"]] @@ -124,8 +135,14 @@ class FlowEdgeParam(TypedDict, total=False): The target is either another node in the same flow (`NodeTarget`) or a different assistant (`AssistantTarget`). Multiple edges may share a - `start_node_id`; the runtime evaluates them in the order they're - declared and takes the first whose condition is true. + `start_node_id`. On calls, `expression` conditions are evaluated before + the model turn and take precedence over `llm` conditions regardless of + declaration order, while `llm` conditions are offered to the assistant's + model as transition tools and fire when the model selects one. On chat + channels, an `expression` condition that is true when the turn begins + routes before the reply is generated; all conditioned edges that remain + are considered together in declaration order after the reply, and the + first true one wins. """ id: Required[str] diff --git a/src/telnyx/types/ai/flow_node_req_param.py b/src/telnyx/types/ai/flow_node_req_param.py index b2e187275..0f76d57eb 100644 --- a/src/telnyx/types/ai/flow_node_req_param.py +++ b/src/telnyx/types/ai/flow_node_req_param.py @@ -92,7 +92,7 @@ class FlowNodeReqParam(TypedDict, total=False): """Node kind discriminator. `prompt` (default) is an LLM-driven step; `tool` is a standalone tool execution - (see `ToolNodeReq`). + and `speak` a scripted message (see `ToolNodeReq` / `SpeakNodeReq`). """ voice_settings: InferenceEmbeddingVoiceSettingsParam diff --git a/src/telnyx/types/ai/inference_embedding.py b/src/telnyx/types/ai/inference_embedding.py index 5f73a72f2..587494129 100644 --- a/src/telnyx/types/ai/inference_embedding.py +++ b/src/telnyx/types/ai/inference_embedding.py @@ -18,6 +18,8 @@ from .conversation_flow import ConversationFlow from .messaging_settings import MessagingSettings from .telephony_settings import TelephonySettings +from .websocket_settings import WebsocketSettings +from .delegation_settings import DelegationSettings from .assistant_a2_a_agent import AssistantA2AAgent from .assistant_mcp_server import AssistantMcpServer from .assistant_integration import AssistantIntegration @@ -69,6 +71,17 @@ class InferenceEmbedding(BaseModel): conversation_flow: Optional[ConversationFlow] = None """Conversation flow as returned by the API.""" + delegation_settings: Optional[DelegationSettings] = None + """ + Splits the conversation between a frontend model that talks to the caller and a + backend model that does the work. On the GPT-Live route the frontend model + cannot call tools at all — when it needs something done it raises a delegation + and waits. On the chat completion route the frontend keeps a single `delegate` + tool that returns immediately, so the conversation carries on while the backend + works. Either way the backend's answer is spoken as commentary or kept as silent + context, depending on `speak_results`. Beta feature. + """ + description: Optional[str] = None dynamic_variables: Optional[Dict[str, object]] = None @@ -202,5 +215,14 @@ class InferenceEmbedding(BaseModel): voice_settings: Optional[InferenceEmbeddingVoiceSettings] = None + websocket_settings: Optional[WebsocketSettings] = None + """ + Streams conversation and telephony events to a WebSocket server you host, and + accepts messages injected back into the conversation. Telnyx opens the + connection as a client, once per conversation. Delivery is best effort + throughout: while the connection is down events are dropped rather than queued, + and no socket failure is ever allowed to affect the call. Beta feature. + """ + widget_settings: Optional[WidgetSettings] = None """Configuration settings for the assistant's web widget.""" diff --git a/src/telnyx/types/ai/inference_embedding_webhook_tool_params.py b/src/telnyx/types/ai/inference_embedding_webhook_tool_params.py index 6a4f28a0c..92432807f 100644 --- a/src/telnyx/types/ai/inference_embedding_webhook_tool_params.py +++ b/src/telnyx/types/ai/inference_embedding_webhook_tool_params.py @@ -223,12 +223,6 @@ class Webhook(BaseModel): dot-notation path to the value in the response body. """ - timeout_ms: Optional[int] = None - """The maximum number of milliseconds to wait for the webhook to respond. - - Only applicable when async is false. - """ - class InferenceEmbeddingWebhookToolParams(BaseModel): type: Literal["webhook"] @@ -245,3 +239,11 @@ class InferenceEmbeddingWebhookToolParams(BaseModel): creates an inline duplicate (rejected with error code 10015 when the type allows only one instance per assistant). """ + + timeout_ms: Optional[int] = None + """ + The maximum number of milliseconds to wait for the webhook to respond before the + tool call is aborted. Set this at the tool level, as a sibling of `type` — a + `timeout_ms` nested inside the `webhook` object is stored but not applied, and + the tool runs at this default instead. Applies when `webhook.async` is false. + """ diff --git a/src/telnyx/types/ai/inference_embedding_webhook_tool_params_param.py b/src/telnyx/types/ai/inference_embedding_webhook_tool_params_param.py index 92ef036d6..810c50e51 100644 --- a/src/telnyx/types/ai/inference_embedding_webhook_tool_params_param.py +++ b/src/telnyx/types/ai/inference_embedding_webhook_tool_params_param.py @@ -224,14 +224,16 @@ class Webhook(_WebhookReservedKeywords, total=False): dot-notation path to the value in the response body. """ - timeout_ms: int - """The maximum number of milliseconds to wait for the webhook to respond. - - Only applicable when async is false. - """ - class InferenceEmbeddingWebhookToolParamsParam(TypedDict, total=False): type: Required[Literal["webhook"]] webhook: Required[Webhook] + + timeout_ms: int + """ + The maximum number of milliseconds to wait for the webhook to respond before the + tool call is aborted. Set this at the tool level, as a sibling of `type` — a + `timeout_ms` nested inside the `webhook` object is stored but not applied, and + the tool runs at this default instead. Applies when `webhook.async` is false. + """ diff --git a/src/telnyx/types/ai/memory/__init__.py b/src/telnyx/types/ai/memory/__init__.py index ffe1dc349..9d43f66b9 100644 --- a/src/telnyx/types/ai/memory/__init__.py +++ b/src/telnyx/types/ai/memory/__init__.py @@ -4,13 +4,30 @@ from typing import TYPE_CHECKING, Any +from .namespace_create_params import NamespaceCreateParams as NamespaceCreateParams + if TYPE_CHECKING: + from .namespace import Namespace as Namespace + from .namespace_list_response import NamespaceListResponse as NamespaceListResponse + from .namespace_create_response import NamespaceCreateResponse as NamespaceCreateResponse from .namespace_retrieve_response import NamespaceRetrieveResponse as NamespaceRetrieveResponse def __getattr__(name: str) -> Any: + if name == "Namespace": + from .namespace import Namespace + + return Namespace + if name == "NamespaceCreateResponse": + from .namespace_create_response import NamespaceCreateResponse + + return NamespaceCreateResponse if name == "NamespaceRetrieveResponse": from .namespace_retrieve_response import NamespaceRetrieveResponse return NamespaceRetrieveResponse + if name == "NamespaceListResponse": + from .namespace_list_response import NamespaceListResponse + + return NamespaceListResponse raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/telnyx/types/ai/memory/namespace.py b/src/telnyx/types/ai/memory/namespace.py new file mode 100644 index 000000000..cf9f25eb2 --- /dev/null +++ b/src/telnyx/types/ai/memory/namespace.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ...._models import BaseModel + +__all__ = ["Namespace"] + + +class Namespace(BaseModel): + """An isolated memory store within your organization.""" + + id: str + """The namespace's unique identifier.""" + + name: str + """The namespace's name, used in the path. + + `default` exists for every organization. + """ diff --git a/src/telnyx/types/ai/memory/namespace_create_params.py b/src/telnyx/types/ai/memory/namespace_create_params.py new file mode 100644 index 000000000..bc6d932fa --- /dev/null +++ b/src/telnyx/types/ai/memory/namespace_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["NamespaceCreateParams"] + + +class NamespaceCreateParams(TypedDict, total=False): + name: Required[str] + """A name for the new namespace, unique within your organization.""" diff --git a/src/telnyx/types/ai/memory/namespace_create_response.py b/src/telnyx/types/ai/memory/namespace_create_response.py new file mode 100644 index 000000000..8d4ccf139 --- /dev/null +++ b/src/telnyx/types/ai/memory/namespace_create_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .namespace import Namespace +from ...._models import BaseModel + +__all__ = ["NamespaceCreateResponse"] + + +class NamespaceCreateResponse(BaseModel): + data: Namespace + """An isolated memory store within your organization.""" diff --git a/src/telnyx/types/ai/memory/namespace_list_response.py b/src/telnyx/types/ai/memory/namespace_list_response.py new file mode 100644 index 000000000..cc714aacb --- /dev/null +++ b/src/telnyx/types/ai/memory/namespace_list_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List + +from .namespace import Namespace +from ...._models import BaseModel + +__all__ = ["NamespaceListResponse"] + + +class NamespaceListResponse(BaseModel): + data: List[Namespace] diff --git a/src/telnyx/types/ai/openai/chat_create_completion_params.py b/src/telnyx/types/ai/openai/chat_create_completion_params.py index 8595202f5..546c8d351 100644 --- a/src/telnyx/types/ai/openai/chat_create_completion_params.py +++ b/src/telnyx/types/ai/openai/chat_create_completion_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Union, Iterable +from typing import Dict, Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict from ...._types import SequenceNotStr @@ -70,8 +70,15 @@ class ChatCreateCompletionParams(TypedDict, total=False): `content` of `message`. """ - max_tokens: int - """Maximum number of completion tokens the model should generate.""" + max_tokens: Optional[int] + """Maximum number of completion (output) tokens the model may generate per request. + + Defaults to 8192 when omitted or `null`. Set a higher value to allow longer + completions. The model's `max_completion_tokens` metadata (see + `GET /ai/models`), when set, caps both the default and any larger explicit + value. Reasoning models consume this budget across reasoning and answer tokens + combined. + """ min_p: float """ diff --git a/src/telnyx/types/ai/tool_node.py b/src/telnyx/types/ai/tool_node.py index 370cec47b..33afa7cf9 100644 --- a/src/telnyx/types/ai/tool_node.py +++ b/src/telnyx/types/ai/tool_node.py @@ -22,10 +22,21 @@ class ToolNode(BaseModel): """ID of the single shared (org-level) tool this node executes. When the flow reaches this node the tool runs as a deliberate step (no LLM - turn); its outgoing `tool_result` edges then route on the outcome. Arguments are - filled from the conversation's dynamic variables by name — a dynamic variable - whose name matches one of the tool's parameters supplies that argument. - Cross-validated against the org's shared tools on write. + turn); its outgoing `llm` / `expression` edges route the flow on the tool's + outcome. Arguments are filled from the conversation's dynamic variables by name + — a dynamic variable whose name matches one of the tool's parameters supplies + that argument. Cross-validated against the org's shared tools on write. + """ + + message: Optional[str] = None + """ + Optional message delivered to the user verbatim immediately before the tool + executes — an announcement such as 'One moment while I look that up.' No LLM + turn and no customer turn: the message is spoken/sent, then the tool runs, in + the same deterministic step. `{{variable}}` placeholders are interpolated from + the conversation's dynamic variables (unresolved → empty string); the tool's own + result is not yet available when the message is rendered. Omit for a silent tool + step. """ name: Optional[str] = None diff --git a/src/telnyx/types/ai/tool_node_req_param.py b/src/telnyx/types/ai/tool_node_req_param.py index c95d643f8..055ac4e8a 100644 --- a/src/telnyx/types/ai/tool_node_req_param.py +++ b/src/telnyx/types/ai/tool_node_req_param.py @@ -15,7 +15,10 @@ class ToolNodeReqParam(TypedDict, total=False): Unlike a prompt node, a tool node has no instructions or model — it isn't an LLM turn. Reaching it deterministically runs one shared tool (arguments filled from matching dynamic variables by name), then routes - on the result via outgoing `tool_result` edges. + via outgoing `llm` / `expression` edges, with exactly one `default` + fallback edge required when the node has any outgoing edges (the + tool's outcome is readable as `telnyx_last_tool_status_code` in + `expression` conditions). """ id: Required[str] @@ -25,10 +28,21 @@ class ToolNodeReqParam(TypedDict, total=False): """ID of the single shared (org-level) tool this node executes. When the flow reaches this node the tool runs as a deliberate step (no LLM - turn); its outgoing `tool_result` edges then route on the outcome. Arguments are - filled from the conversation's dynamic variables by name — a dynamic variable - whose name matches one of the tool's parameters supplies that argument. - Cross-validated against the org's shared tools on write. + turn); its outgoing `llm` / `expression` edges route the flow on the tool's + outcome. Arguments are filled from the conversation's dynamic variables by name + — a dynamic variable whose name matches one of the tool's parameters supplies + that argument. Cross-validated against the org's shared tools on write. + """ + + message: str + """ + Optional message delivered to the user verbatim immediately before the tool + executes — an announcement such as 'One moment while I look that up.' No LLM + turn and no customer turn: the message is spoken/sent, then the tool runs, in + the same deterministic step. `{{variable}}` placeholders are interpolated from + the conversation's dynamic variables (unresolved → empty string); the tool's own + result is not yet available when the message is rendered. Omit for a silent tool + step. """ name: str diff --git a/src/telnyx/types/ai/webhook_tool_param.py b/src/telnyx/types/ai/webhook_tool_param.py index 33c87cf9f..949b523df 100644 --- a/src/telnyx/types/ai/webhook_tool_param.py +++ b/src/telnyx/types/ai/webhook_tool_param.py @@ -127,3 +127,11 @@ class WebhookToolParam(TypedDict, total=False): type: Required[Literal["webhook"]] webhook: Required[Webhook] + + timeout_ms: int + """ + The maximum number of milliseconds to wait for the webhook to respond before the + tool call is aborted. Set this at the tool level, as a sibling of `type` — a + `timeout_ms` nested inside the `webhook` object is not applied, and the tool + runs at this default instead. + """ diff --git a/src/telnyx/types/ai/websocket_settings.py b/src/telnyx/types/ai/websocket_settings.py new file mode 100644 index 000000000..21250b236 --- /dev/null +++ b/src/telnyx/types/ai/websocket_settings.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["WebsocketSettings"] + + +class WebsocketSettings(BaseModel): + """ + Streams conversation and telephony events to a WebSocket server you host, and accepts messages injected back into the conversation. Telnyx opens the connection as a client, once per conversation. Delivery is best effort throughout: while the connection is down events are dropped rather than queued, and no socket failure is ever allowed to affect the call. Beta feature. + """ + + auth_ref: Optional[str] = None + """ + Integration secret identifier whose value Telnyx sends as an + `Authorization: Bearer ` header on the upgrade request. Resolved on every + connection attempt, so a rotated secret is picked up by the next reconnect. + """ + + enabled: Optional[bool] = None + """ + Whether Telnyx opens a WebSocket to `url` for each of this assistant's + conversations. Defaults to `false`. + """ + + url: Optional[str] = None + """The `ws://` or `wss://` endpoint Telnyx connects to. + + Required when `enabled` is `true`. Must be externally reachable — localhost, + private IP ranges and `.local` domains are rejected. + """ diff --git a/src/telnyx/types/ai/websocket_settings_param.py b/src/telnyx/types/ai/websocket_settings_param.py new file mode 100644 index 000000000..7f27ec6a1 --- /dev/null +++ b/src/telnyx/types/ai/websocket_settings_param.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["WebsocketSettingsParam"] + + +class WebsocketSettingsParam(TypedDict, total=False): + """ + Streams conversation and telephony events to a WebSocket server you host, and accepts messages injected back into the conversation. Telnyx opens the connection as a client, once per conversation. Delivery is best effort throughout: while the connection is down events are dropped rather than queued, and no socket failure is ever allowed to affect the call. Beta feature. + """ + + auth_ref: str + """ + Integration secret identifier whose value Telnyx sends as an + `Authorization: Bearer ` header on the upgrade request. Resolved on every + connection attempt, so a rotated secret is picked up by the next reconnect. + """ + + enabled: bool + """ + Whether Telnyx opens a WebSocket to `url` for each of this assistant's + conversations. Defaults to `false`. + """ + + url: str + """The `ws://` or `wss://` endpoint Telnyx connects to. + + Required when `enabled` is `true`. Must be externally reachable — localhost, + private IP ranges and `.local` domains are rejected. + """ diff --git a/src/telnyx/types/billing_contact.py b/src/telnyx/types/billing_contact.py index 7141fb0ec..7284fe856 100644 --- a/src/telnyx/types/billing_contact.py +++ b/src/telnyx/types/billing_contact.py @@ -9,10 +9,25 @@ class BillingContact(BaseModel): email: str + """ + The email address of the person Telnyx should contact about billing for this + account. + """ first_name: str + """ + The first name of the person Telnyx should contact about billing for this + account. + """ last_name: str + """ + The last name of the person Telnyx should contact about billing for this + account. + """ phone_number: str - """E.164 format with leading `+`.""" + """ + The phone number of the billing contact, in E.164 format, for example + +12125551234. + """ diff --git a/src/telnyx/types/billing_contact_param.py b/src/telnyx/types/billing_contact_param.py index bbe2c83fb..522a43350 100644 --- a/src/telnyx/types/billing_contact_param.py +++ b/src/telnyx/types/billing_contact_param.py @@ -9,10 +9,25 @@ class BillingContactParam(TypedDict, total=False): email: Required[str] + """ + The email address of the person Telnyx should contact about billing for this + account. + """ first_name: Required[str] + """ + The first name of the person Telnyx should contact about billing for this + account. + """ last_name: Required[str] + """ + The last name of the person Telnyx should contact about billing for this + account. + """ phone_number: Required[str] - """E.164 format with leading `+`.""" + """ + The phone number of the billing contact, in E.164 format, for example + +12125551234. + """ diff --git a/src/telnyx/types/bpo_authorization_input_param.py b/src/telnyx/types/bpo_authorization_input_param.py new file mode 100644 index 000000000..d7b11e47d --- /dev/null +++ b/src/telnyx/types/bpo_authorization_input_param.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BpoAuthorizationInputParam"] + + +class BpoAuthorizationInputParam(TypedDict, total=False): + """ + One authorization to include when creating or updating a DIR: an approved BPO (Business Process Outsourcer) account plus the signed Letter of Authorization the Brand Owner granted it. + """ + + bpo_enterprise_id: Required[str] + """ + Enterprise id of an approved BPO (Business Process Outsourcer) account on your + organization to authorize for this DIR. + """ + + loa_document_id: Required[str] + """ + Id of the signed Letter of Authorization document (uploaded via the Telnyx + Documents API) in which the Brand Owner authorizes this BPO. + """ diff --git a/src/telnyx/types/calls/action_reject_params.py b/src/telnyx/types/calls/action_reject_params.py index 5f92111a5..7f3381e06 100644 --- a/src/telnyx/types/calls/action_reject_params.py +++ b/src/telnyx/types/calls/action_reject_params.py @@ -8,8 +8,13 @@ class ActionRejectParams(TypedDict, total=False): - cause: Required[Literal["CALL_REJECTED", "USER_BUSY"]] - """Cause for call rejection.""" + cause: Required[Literal["CALL_REJECTED", "NOT_FOUND", "TEMPORARILY_UNAVAILABLE", "USER_BUSY"]] + """Cause for call rejection. + + The cause sets the SIP response the caller receives: `USER_BUSY` sends 486 User + Busy, `CALL_REJECTED` sends 603 Decline, `NOT_FOUND` sends 404 Not Found, and + `TEMPORARILY_UNAVAILABLE` sends 480 Temporarily Unavailable. + """ client_state: str """Use this field to add state to every subsequent webhook. diff --git a/src/telnyx/types/dir/dir.py b/src/telnyx/types/dir/dir.py index 68670e198..39b2288d6 100644 --- a/src/telnyx/types/dir/dir.py +++ b/src/telnyx/types/dir/dir.py @@ -36,6 +36,12 @@ class Dir(BaseModel): created_at: Optional[datetime] = None + delete_requested_at: Optional[datetime] = None + """When deletion was requested. + + Set once the DIR enters `delete_requested`; `null` otherwise. + """ + display_name: Optional[str] = None documents: Optional[List[Document]] = None @@ -71,6 +77,10 @@ class Dir(BaseModel): - `infringement_claimed` - a trademark/impersonation claim is open against this DIR. - `permanently_rejected` - terminal; cannot be resubmitted. + - `delete_requested` - you have requested deletion; the DIR still exists and + Telnyx is completing the removal (de-registration and cleanup). A verified DIR + keeps serving its branded identity, and keeps billing, until the removal + finishes. """ submitted_at: Optional[datetime] = None @@ -78,3 +88,9 @@ class Dir(BaseModel): updated_at: Optional[datetime] = None verified_at: Optional[datetime] = None + + webhook_url: Optional[str] = None + """ + `https://` URL that receives webhook notifications for this DIR's + compliance-review outcomes. `null` when not subscribed. + """ diff --git a/src/telnyx/types/dir/phone_number_remove_params.py b/src/telnyx/types/dir/phone_number_remove_params.py index 1e695d958..9d67d3520 100644 --- a/src/telnyx/types/dir/phone_number_remove_params.py +++ b/src/telnyx/types/dir/phone_number_remove_params.py @@ -11,3 +11,7 @@ class PhoneNumberRemoveParams(TypedDict, total=False): phone_numbers: Required[SequenceNotStr[str]] + """ + The phone numbers to remove from this brand, in E.164 format, up to 100 per + request. They must currently be attached to this brand. + """ diff --git a/src/telnyx/types/dir/reference_update_params.py b/src/telnyx/types/dir/reference_update_params.py index a24a9a51c..ac877f1bc 100644 --- a/src/telnyx/types/dir/reference_update_params.py +++ b/src/telnyx/types/dir/reference_update_params.py @@ -14,22 +14,32 @@ class ReferenceUpdateParams(TypedDict, total=False): ref_type: Required[Literal["business", "financial"]] email: str - """Reference contact email address.""" + """The reference's email address. + + We email them scheduling and dial-in instructions before we call, so use an + address they check. + """ full_name: str - """Full name of the reference contact.""" + """The full name of the person we should contact as your reference.""" job_title: Optional[str] - """Job title of the reference contact.""" + """The reference contact's job title, for example CFO or Owner.""" organization: Optional[str] - """Organization the reference contact belongs to.""" + """The name of the organization the reference contact works for.""" phone_e164: str - """Reference phone number in E.164 format.""" + """The reference's phone number in E.164 format, for example +14155550123. + + We call this number during their local business hours. + """ relationship_to_registrant: Optional[str] """How the reference contact is related to the registering business.""" timezone: str - """IANA timezone id for the reference.""" + """The reference's IANA time zone, for example America/New_York. + + We only call during their local 8am to 9pm hours, which is why we need it. + """ diff --git a/src/telnyx/types/dir_bpo_loa_params.py b/src/telnyx/types/dir_bpo_loa_params.py new file mode 100644 index 000000000..8acc2902e --- /dev/null +++ b/src/telnyx/types/dir_bpo_loa_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .signature_payload_param import SignaturePayloadParam + +__all__ = ["DirBpoLoaParams"] + + +class DirBpoLoaParams(TypedDict, total=False): + bpo_enterprise_id: Required[str] + """The approved BPO enterprise the Brand Owner is authorizing. + + Must be a BPO account on the caller's organization that has already been + approved. + """ + + signature: SignaturePayloadParam + """Optional. + + When provided the rendered PDF embeds the signature image, printed name, and + signed-at date. When absent the PDF is returned unsigned so the Brand Owner can + sign externally and the BPO can upload it via the Documents API. + """ diff --git a/src/telnyx/types/dir_delete_response.py b/src/telnyx/types/dir_delete_response.py new file mode 100644 index 000000000..79cc25f3c --- /dev/null +++ b/src/telnyx/types/dir_delete_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DirDeleteResponse", "Data"] + + +class Data(BaseModel): + id: str + """Id of the DIR whose deletion was requested.""" + + status: Literal["delete_requested"] + """ + Always `delete_requested`: the DIR has been queued for removal, not yet removed. + """ + + +class DirDeleteResponse(BaseModel): + data: Data diff --git a/src/telnyx/types/dir_new_loa_params.py b/src/telnyx/types/dir_new_loa_params.py index eb968bef9..46c66bdee 100644 --- a/src/telnyx/types/dir_new_loa_params.py +++ b/src/telnyx/types/dir_new_loa_params.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import Optional from typing_extensions import Required, TypedDict from .._types import SequenceNotStr +from .signature_payload_param import SignaturePayloadParam from .enterprises.reputation.agent_input_param import AgentInputParam -__all__ = ["DirNewLoaParams", "Signature"] +__all__ = ["DirNewLoaParams"] class DirNewLoaParams(TypedDict, total=False): @@ -24,26 +24,10 @@ class DirNewLoaParams(TypedDict, total=False): Omit when the enterprise works directly with Telnyx. """ - signature: Signature + signature: SignaturePayloadParam """Optional. When provided the rendered PDF embeds the signature image, printed name, and signed-at date. When absent the PDF is returned unsigned so the customer can sign externally and upload it via the Documents API. """ - - -class Signature(TypedDict, total=False): - """Optional. - - When provided the rendered PDF embeds the signature image, printed name, and signed-at date. When absent the PDF is returned unsigned so the customer can sign externally and upload it via the Documents API. - """ - - image_base64: Required[str] - """PNG image, base64-encoded.""" - - signer_name: Optional[str] - """Optional. - - When absent the rendered PDF falls back to the enterprise contact's legal name. - """ diff --git a/src/telnyx/types/dir_retrieve_bpo_authorizations_params.py b/src/telnyx/types/dir_retrieve_bpo_authorizations_params.py new file mode 100644 index 000000000..677023fcf --- /dev/null +++ b/src/telnyx/types/dir_retrieve_bpo_authorizations_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["DirRetrieveBpoAuthorizationsParams"] + + +class DirRetrieveBpoAuthorizationsParams(TypedDict, total=False): + page_number: Annotated[int, PropertyInfo(alias="page[number]")] + """1-based page number. + + Out-of-range values return an empty page with correct meta. + """ + + page_size: Annotated[int, PropertyInfo(alias="page[size]")] + """Items per page. Maximum 250; values above are clamped to 250.""" diff --git a/src/telnyx/types/dir_retrieve_bpo_authorizations_response.py b/src/telnyx/types/dir_retrieve_bpo_authorizations_response.py new file mode 100644 index 000000000..3fa3b04ac --- /dev/null +++ b/src/telnyx/types/dir_retrieve_bpo_authorizations_response.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .branded_calling_pagination_meta import BrandedCallingPaginationMeta + +__all__ = ["DirRetrieveBpoAuthorizationsResponse", "Data"] + + +class Data(BaseModel): + """A single authorization of a BPO (Business Process Outsourcer) account on a DIR.""" + + bpo_enterprise_id: str + """The authorized BPO account's enterprise id.""" + + loa_document_id: str + """Id of the signed Letter of Authorization document submitted for this BPO. + + Send it back unchanged in `bpo_authorizations` when updating the DIR to keep + this authorization and its review state. + """ + + record_type: Literal["bpo_authorization"] + """Always `bpo_authorization`.""" + + status: Literal["pending", "approved", "rejected"] + """Review state of this authorization. + + `pending` on create or when the Letter of Authorization is re-uploaded; an admin + moves it to `approved` or `rejected`. Only an `approved` authorization adds the + BPO to this DIR's authorized callers in the branded calling registry. + """ + + rejection_reason: Optional[str] = None + """Why the authorization was rejected. `null` unless `status` is `rejected`.""" + + +class DirRetrieveBpoAuthorizationsResponse(BaseModel): + """Paginated list of a DIR's BPO authorizations.""" + + data: List[Data] + + meta: BrandedCallingPaginationMeta + """JSON:API pagination metadata returned with every paginated list response. + + Page numbering is 1-based. `page_size` reports the number of items actually + returned in `data` for this page; the requested size is taken from the + `page[size]` query parameter. + """ diff --git a/src/telnyx/types/dir_status.py b/src/telnyx/types/dir_status.py index 207a1bff6..81ade0467 100644 --- a/src/telnyx/types/dir_status.py +++ b/src/telnyx/types/dir_status.py @@ -17,4 +17,5 @@ "expired", "infringement_claimed", "permanently_rejected", + "delete_requested", ] diff --git a/src/telnyx/types/dir_update_infringement_params.py b/src/telnyx/types/dir_update_infringement_params.py index 6c1a3df15..bd86c90e0 100644 --- a/src/telnyx/types/dir_update_infringement_params.py +++ b/src/telnyx/types/dir_update_infringement_params.py @@ -19,7 +19,10 @@ class DirUpdateInfringementParams(TypedDict, total=False): """Must be `true`.""" certify_no_infringement: Required[Literal[True]] - """Must be `true`.""" + """ + Check to certify that the brand no longer infringes anyone else's trademark or + intellectual property. + """ certify_no_shaft_content: Required[Literal[True]] """Must be `true`.""" @@ -30,6 +33,10 @@ class DirUpdateInfringementParams(TypedDict, total=False): call_reasons: Optional[SequenceNotStr[str]] display_name: Optional[str] + """ + The business name shown to call recipients, 1 to 35 characters, no emoji, not + blank. + """ documents: Optional[Iterable[DocumentParam]] """Append-only supporting documents to attach while resolving the claim (e.g. diff --git a/src/telnyx/types/dir_update_params.py b/src/telnyx/types/dir_update_params.py index ba7fae78e..a23418cf6 100644 --- a/src/telnyx/types/dir_update_params.py +++ b/src/telnyx/types/dir_update_params.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import Iterable +from typing import Iterable, Optional from typing_extensions import TypedDict from .._types import SequenceNotStr from .document_param import DocumentParam +from .bpo_authorization_input_param import BpoAuthorizationInputParam __all__ = ["DirUpdateParams"] @@ -24,6 +25,17 @@ class DirUpdateParams(TypedDict, total=False): Must be a real individual. """ + bpo_authorizations: Iterable[BpoAuthorizationInputParam] + """Optional. + + Replace this DIR's authorized BPO (Business Process Outsourcer) accounts with + these, each with its signed Letter of Authorization. The supplied list replaces + the current one: a BPO left out has its authorization removed, and a new BPO (or + a changed Letter of Authorization) is created `pending` admin review. Send an + empty list to clear all authorizations; omit the field to leave them unchanged. + Editing this list does not re-vet the DIR. Maximum 10. + """ + call_reasons: SequenceNotStr[str] """1–10 reasons your business calls customers. @@ -67,3 +79,10 @@ class DirUpdateParams(TypedDict, total=False): Set to true if your organization places calls on behalf of other enterprises (BPO/reseller). Updating this triggers re-vetting on next submit. """ + + webhook_url: Optional[str] + """ + Optional `https://` URL that receives webhook notifications when this DIR's + compliance review completes. Send `null` to clear. Changing only this field on a + `verified` DIR does not re-vet it. Maximum 2048 characters. + """ diff --git a/src/telnyx/types/document.py b/src/telnyx/types/document.py index 2076b3696..de2b4128e 100644 --- a/src/telnyx/types/document.py +++ b/src/telnyx/types/document.py @@ -40,3 +40,4 @@ class Document(BaseModel): """ description: Optional[str] = None + """An optional note describing this document, for example what it proves.""" diff --git a/src/telnyx/types/document_param.py b/src/telnyx/types/document_param.py index 8d38fcf26..5ca48ce60 100644 --- a/src/telnyx/types/document_param.py +++ b/src/telnyx/types/document_param.py @@ -39,3 +39,4 @@ class DocumentParam(TypedDict, total=False): """ description: str + """An optional note describing this document, for example what it proves.""" diff --git a/src/telnyx/types/enterprise_create_params.py b/src/telnyx/types/enterprise_create_params.py index 652619535..7bb2ad70e 100644 --- a/src/telnyx/types/enterprise_create_params.py +++ b/src/telnyx/types/enterprise_create_params.py @@ -21,6 +21,11 @@ class EnterpriseCreateParams(TypedDict, total=False): """ISO 3166-1 alpha-2 country code. Currently `US` and `CA` are supported.""" doing_business_as: Required[str] + """ + The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + """ fein: Required[str] """ @@ -74,12 +79,23 @@ class EnterpriseCreateParams(TypedDict, total=False): "hotel", ] ] - """Industry classification.""" + """The industry your business operates in. + + Choose the closest match from the list; if your value is not accepted, pick the + nearest category. + """ jurisdiction_of_incorporation: Required[str] + """ + The state, province, or country where your business was legally incorporated, + for example Delaware. + """ legal_name: Required[str] - """Legal name of the enterprise.""" + """ + Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. + """ number_of_employees: Required[Literal["1-10", "11-50", "51-200", "201-500", "501-2000", "2001-10000", "10001+"]] """Approximate headcount range. @@ -115,27 +131,53 @@ class EnterpriseCreateParams(TypedDict, total=False): """ website: Required[str] + """Your business's public website address, including https://. + + Leave blank if your business has no website. + """ corporate_registration_number: Optional[str] - """Optional corporate-registration / company-number identifier.""" + """ + The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. + """ customer_reference: str - """Optional free-form string the caller can attach for their own bookkeeping. + """Your own label for this account. - Telnyx does not interpret it. + Enter any reference that helps you find it in your records. Telnyx does not use + it during vetting. """ dun_bradstreet_number: Optional[str] - """Optional D-U-N-S Number.""" + """ + Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. + """ primary_business_domain_sic_code: Optional[str] - """Optional SIC code for the primary line of business.""" + """ + The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. + """ professional_license_number: Optional[str] - """Optional professional-license number for regulated industries.""" + """ + If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. + """ role_type: Literal["enterprise", "bpo"] """ - `enterprise` for an organization registering its own DIRs; `bpo` for a Business - Process Outsourcer placing calls on behalf of one or more enterprises. + `enterprise` for an organization registering its own DIRs (the default, and the + right choice when the calls display your own brand). `bpo` for a Business + Process Outsourcer: a call center that places calls on behalf of other + enterprises and displays their brand. A `bpo` enterprise describes the call + center itself and cannot own a DIR. Each client the call center calls for gets + its own `enterprise` in the same account, with the client's DIR under it; that + DIR is then linked to the `bpo` enterprise through `bpo_authorizations`. Fixed + at creation. """ diff --git a/src/telnyx/types/enterprise_list_params.py b/src/telnyx/types/enterprise_list_params.py index c3783bcef..47f7352df 100644 --- a/src/telnyx/types/enterprise_list_params.py +++ b/src/telnyx/types/enterprise_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._utils import PropertyInfo @@ -13,6 +13,12 @@ class EnterpriseListParams(TypedDict, total=False): filter_legal_name_contains: Annotated[str, PropertyInfo(alias="filter[legal_name][contains]")] """Case-insensitive partial match on legal name.""" + filter_role_type: Annotated[Literal["enterprise", "bpo"], PropertyInfo(alias="filter[role_type]")] + """ + Only return enterprises of this type: `bpo` for call-center (BPO) enterprises, + `enterprise` for normal enterprises. Omit to return both. + """ + legal_name: str """Filter by legal name (partial match).""" diff --git a/src/telnyx/types/enterprise_public.py b/src/telnyx/types/enterprise_public.py index 78a483d79..25d8842b6 100644 --- a/src/telnyx/types/enterprise_public.py +++ b/src/telnyx/types/enterprise_public.py @@ -4,6 +4,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .billing_contact import BillingContact @@ -20,6 +21,20 @@ class EnterprisePublic(BaseModel): billing_contact: Optional[BillingContact] = None + bpo_verification_rejection_reason: Optional[str] = None + """ + Reason Telnyx rejected the BPO (Business Process Outsourcer) verification, when + `bpo_verification_status` is `rejected`; `null` otherwise. + """ + + bpo_verification_status: Optional[Literal["pending", "approved", "rejected"]] = None + """Whether Telnyx has approved this BPO (Business Process Outsourcer) account. + + Only set for accounts created with `role_type` `bpo`; `null` for normal + enterprises. A BPO enterprise must be `approved` before a DIR can be linked to + it through `bpo_authorizations`. + """ + branded_calling_enabled: Optional[bool] = None """ True once Branded Calling has been activated on this enterprise (see @@ -27,28 +42,66 @@ class EnterprisePublic(BaseModel): """ corporate_registration_number: Optional[str] = None - """Optional corporate-registration / company-number identifier.""" + """ + The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. + """ country_code: Optional[str] = None created_at: Optional[datetime] = None customer_reference: Optional[str] = None + """Your own label for this account. + + Enter any reference that helps you find it in your records. Telnyx does not use + it during vetting. + """ doing_business_as: Optional[str] = None + """ + The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + """ dun_bradstreet_number: Optional[str] = None - """Optional D-U-N-S Number issued by Dun & Bradstreet.""" + """ + Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. + """ fein: Optional[str] = None + """ + US Federal Employer Identification Number (`NN-NNNNNNN`) or Canadian equivalent. + """ industry: Optional[str] = None + """The industry your business operates in. + + Choose the closest match from the list; if your value is not accepted, pick the + nearest category. + """ jurisdiction_of_incorporation: Optional[str] = None + """ + The state, province, or country where your business was legally incorporated, + for example Delaware. + """ legal_name: Optional[str] = None + """ + Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. + """ number_of_employees: Optional[str] = None + """Approximate headcount range. + + Used for vetting heuristics; pick the bucket that contains your current employee + count. + """ number_reputation_enabled: Optional[bool] = None """ @@ -59,19 +112,41 @@ class EnterprisePublic(BaseModel): organization_contact: Optional[OrganizationContact] = None organization_legal_type: Optional[str] = None + """Legal-entity form. Pick the form that matches your incorporation documents: + + - `corporation` - C-corp or S-corp. + - `llc` - limited liability company. + - `partnership` - general/limited partnership. + - `nonprofit` - non-profit corporation, charitable trust, or + 501(c)(3)/equivalent. + - `other` - anything else (sole proprietorships, government bodies, DBAs, etc.). + You may be asked for additional documents during vetting. + """ organization_physical_address: Optional[PhysicalAddress] = None organization_type: Optional[str] = None primary_business_domain_sic_code: Optional[str] = None - """Optional SIC code for the primary line of business.""" + """ + The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. + """ professional_license_number: Optional[str] = None - """Optional professional-license number for regulated industries.""" + """ + If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. + """ - role_type: Optional[str] = None + role_type: Optional[Literal["enterprise", "bpo"]] = None updated_at: Optional[datetime] = None website: Optional[str] = None + """Your business's public website address, including https://. + + Leave blank if your business has no website. + """ diff --git a/src/telnyx/types/enterprise_update_params.py b/src/telnyx/types/enterprise_update_params.py index 26f5d95f5..120153eeb 100644 --- a/src/telnyx/types/enterprise_update_params.py +++ b/src/telnyx/types/enterprise_update_params.py @@ -18,14 +18,36 @@ class EnterpriseUpdateParams(TypedDict, total=False): billing_contact: BillingContactParam corporate_registration_number: Optional[str] + """ + The official number your company received when it was legally registered or + incorporated (for example from your state or national business registry). It is + on your certificate of incorporation. + """ customer_reference: str + """Your own label for this account. + + Enter any reference that helps you find it in your records. Telnyx does not use + it during vetting. + """ doing_business_as: str + """ + The trade name your business operates under if it is different from your legal + name, also called a Doing Business As (DBA) name. Leave blank if you only use + your legal name. + """ dun_bradstreet_number: Optional[str] + """ + Your optional 9-digit D-U-N-S Number issued by Dun & Bradstreet, a unique + identifier for your business. Leave blank if you do not have one. + """ fein: str + """ + US Federal Employer Identification Number (`NN-NNNNNNN`) or Canadian equivalent. + """ industry: Literal[ "accounting", @@ -72,23 +94,63 @@ class EnterpriseUpdateParams(TypedDict, total=False): "hospitality", "hotel", ] + """The industry your business operates in. + + Choose the closest match from the list; if your value is not accepted, pick the + nearest category. + """ jurisdiction_of_incorporation: str - """Updated state/province/country of incorporation. Optional on update.""" + """ + The state, province, or country where your business was legally incorporated, + for example Delaware. + """ legal_name: str - """Legal name of the enterprise.""" + """ + Your business's full registered legal name, exactly as it appears on your + incorporation or tax documents, 3 to 64 characters. + """ number_of_employees: str + """Approximate headcount range. + + Used for vetting heuristics; pick the bucket that contains your current employee + count. + """ organization_contact: OrganizationContactParam organization_legal_type: str + """Legal-entity form. Pick the form that matches your incorporation documents: + + - `corporation` - C-corp or S-corp. + - `llc` - limited liability company. + - `partnership` - general/limited partnership. + - `nonprofit` - non-profit corporation, charitable trust, or + 501(c)(3)/equivalent. + - `other` - anything else (sole proprietorships, government bodies, DBAs, etc.). + You may be asked for additional documents during vetting. + """ organization_physical_address: PhysicalAddressParam primary_business_domain_sic_code: Optional[str] + """ + The 4-digit Standard Industrial Classification code for your main line of + business, which tells us what industry you operate in. Look it up in the SIC + code directory if you are unsure. + """ professional_license_number: Optional[str] + """ + If your business operates under a professional license (for example legal, + medical, or financial services), enter the license number issued by the + licensing authority. Leave blank if it does not apply. + """ website: str + """Your business's public website address, including https://. + + Leave blank if your business has no website. + """ diff --git a/src/telnyx/types/enterprises/__init__.py b/src/telnyx/types/enterprises/__init__.py index 3bf133157..56e82c16c 100644 --- a/src/telnyx/types/enterprises/__init__.py +++ b/src/telnyx/types/enterprises/__init__.py @@ -8,6 +8,7 @@ from .dir_create_params import DirCreateParams as DirCreateParams from .reputation_enable_params import ReputationEnableParams as ReputationEnableParams from .reputation_check_frequency import ReputationCheckFrequency as ReputationCheckFrequency +from .verify_email_confirm_params import VerifyEmailConfirmParams as VerifyEmailConfirmParams from .reputation_update_frequency_params import ReputationUpdateFrequencyParams as ReputationUpdateFrequencyParams if TYPE_CHECKING: @@ -15,6 +16,9 @@ from .enterprise_reputation_public_wrapped import ( EnterpriseReputationPublicWrapped as EnterpriseReputationPublicWrapped, ) + from .enterprise_email_verification_status_wrapped import ( + EnterpriseEmailVerificationStatusWrapped as EnterpriseEmailVerificationStatusWrapped, + ) def __getattr__(name: str) -> Any: @@ -26,4 +30,8 @@ def __getattr__(name: str) -> Any: from .enterprise_reputation_public_wrapped import EnterpriseReputationPublicWrapped return EnterpriseReputationPublicWrapped + if name == "EnterpriseEmailVerificationStatusWrapped": + from .enterprise_email_verification_status_wrapped import EnterpriseEmailVerificationStatusWrapped + + return EnterpriseEmailVerificationStatusWrapped raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/telnyx/types/enterprises/dir_create_params.py b/src/telnyx/types/enterprises/dir_create_params.py index b14e3f73f..c63e6da32 100644 --- a/src/telnyx/types/enterprises/dir_create_params.py +++ b/src/telnyx/types/enterprises/dir_create_params.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import Iterable +from typing import Iterable, Optional from typing_extensions import Literal, Required, TypedDict from ..._types import SequenceNotStr from ..document_param import DocumentParam +from ..bpo_authorization_input_param import BpoAuthorizationInputParam __all__ = ["DirCreateParams"] @@ -32,7 +33,10 @@ class DirCreateParams(TypedDict, total=False): """ certify_brand_is_accurate: Required[Literal[True]] - """Must be `true`.""" + """Certification that the DIR information is accurate. + + Must be `true` for the DIR to be submitted for vetting. + """ certify_ip_ownership: Required[Literal[True]] """Must be `true`. Confirms ownership of any logos/trademarks shown.""" @@ -47,6 +51,16 @@ class DirCreateParams(TypedDict, total=False): display_name: Required[str] """Name shown to call recipients. No emoji; not whitespace-only.""" + bpo_authorizations: Iterable[BpoAuthorizationInputParam] + """Optional. + + Approved BPO (Business Process Outsourcer) accounts on your organization + authorized to place branded calls for this DIR, each with the signed Letter of + Authorization the Brand Owner granted it. Each authorization starts `pending` + and takes effect only after an admin reviews its Letter of Authorization. Omit + or send an empty list to authorize no BPO on this DIR. Maximum 10. + """ + documents: Iterable[DocumentParam] """Supporting documents. Each `document_id` may appear at most once on a DIR.""" @@ -58,3 +72,10 @@ class DirCreateParams(TypedDict, total=False): Set to true if your organization places calls on behalf of other enterprises (BPO/reseller). """ + + webhook_url: Optional[str] + """ + Optional `https://` URL that receives webhook notifications when this DIR's + compliance review completes (rejection outcomes include structured rejection + reasons). Maximum 2048 characters. + """ diff --git a/src/telnyx/types/enterprises/enterprise_email_verification_status_wrapped.py b/src/telnyx/types/enterprises/enterprise_email_verification_status_wrapped.py new file mode 100644 index 000000000..75c57ea2d --- /dev/null +++ b/src/telnyx/types/enterprises/enterprise_email_verification_status_wrapped.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["EnterpriseEmailVerificationStatusWrapped", "Data"] + + +class Data(BaseModel): + """Verification state for an enterprise account's contact email.""" + + email_verified: bool + """Whether the enterprise account's contact email has been confirmed.""" + + record_type: Literal["email_verification"] + """Always `email_verification`.""" + + status: Literal["sent", "verified"] + """`sent` after a code is emailed; `verified` after a successful confirm.""" + + expires_at: Optional[datetime] = None + """When the code just sent stops being accepted. + + Present on a send response; null on a confirm response. + """ + + sends_remaining_today: Optional[int] = None + """How many more codes may be requested for this enterprise account today. + + Present on a send response; null on a confirm response. + """ + + +class EnterpriseEmailVerificationStatusWrapped(BaseModel): + data: Data + """Verification state for an enterprise account's contact email.""" diff --git a/src/telnyx/types/enterprises/reputation/agent_input_param.py b/src/telnyx/types/enterprises/reputation/agent_input_param.py index 1ec225aa4..fef34456a 100644 --- a/src/telnyx/types/enterprises/reputation/agent_input_param.py +++ b/src/telnyx/types/enterprises/reputation/agent_input_param.py @@ -15,25 +15,56 @@ class AgentInputParam(TypedDict, total=False): """ administrative_area: Required[str] + """ + The state or province of the partner's address, as its code, for example IL or + ON. + """ city: Required[str] + """The city of the partner's address.""" contact_email: Required[str] + """The email address of the contact person at the partner.""" contact_name: Required[str] + """The name of a contact person at the partner.""" contact_phone: Required[str] + """ + The phone number of the contact person at the partner, in E.164 format, for + example +13125550000. + """ contact_title: Required[str] + """The job title of the contact person at the partner.""" country: Required[str] + """The two-letter country code of the partner's address, for example US.""" legal_name: Required[str] + """ + The legal name of the third-party partner or reseller managing these numbers on + your behalf. + """ postal_code: Required[str] + """The postal or ZIP code of the partner's address.""" street_address: Required[str] + """ + The street address of the partner, including the building number and street + name. + """ dba: Optional[str] + """ + The trade name (Doing Business As) the partner operates under, if different from + its legal name. Leave blank if it does not apply. + """ extended_address: Optional[str] + """An optional second address line for the partner, such as a suite, unit, or + floor. + + Leave blank if it does not apply. + """ diff --git a/src/telnyx/types/enterprises/reputation/loa_render_params.py b/src/telnyx/types/enterprises/reputation/loa_render_params.py index 54da3a572..f30109d7e 100644 --- a/src/telnyx/types/enterprises/reputation/loa_render_params.py +++ b/src/telnyx/types/enterprises/reputation/loa_render_params.py @@ -5,13 +5,11 @@ from typing import Optional from typing_extensions import Required, TypedDict -from .agent_input_param import AgentInputParam - -__all__ = ["LoaRenderParams", "Signature"] +__all__ = ["LoaRenderParams", "Agent", "Signature"] class LoaRenderParams(TypedDict, total=False): - agent: AgentInputParam + agent: Agent """Third-party reseller / partner managing the enterprise's phone numbers. Omit when the enterprise works directly with Telnyx. @@ -24,6 +22,37 @@ class LoaRenderParams(TypedDict, total=False): """ +class Agent(TypedDict, total=False): + """Third-party reseller / partner managing the enterprise's phone numbers. + + Omit when the enterprise works directly with Telnyx. + """ + + administrative_area: Required[str] + + city: Required[str] + + contact_email: Required[str] + + contact_name: Required[str] + + contact_phone: Required[str] + + contact_title: Required[str] + + country: Required[str] + + legal_name: Required[str] + + postal_code: Required[str] + + street_address: Required[str] + + dba: Optional[str] + + extended_address: Optional[str] + + class Signature(TypedDict, total=False): """Optional signature embedded in the rendered PDF. diff --git a/src/telnyx/types/enterprises/verify_email_confirm_params.py b/src/telnyx/types/enterprises/verify_email_confirm_params.py new file mode 100644 index 000000000..fb11ed796 --- /dev/null +++ b/src/telnyx/types/enterprises/verify_email_confirm_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["VerifyEmailConfirmParams"] + + +class VerifyEmailConfirmParams(TypedDict, total=False): + code: Required[str] + """The 6-digit code sent to the enterprise account's contact email.""" diff --git a/src/telnyx/types/infringement_claim.py b/src/telnyx/types/infringement_claim.py index 3e73a1433..a1203fee3 100644 --- a/src/telnyx/types/infringement_claim.py +++ b/src/telnyx/types/infringement_claim.py @@ -50,6 +50,10 @@ class Dir(BaseModel): - `infringement_claimed` - a trademark/impersonation claim is open against this DIR. - `permanently_rejected` - terminal; cannot be resubmitted. + - `delete_requested` - you have requested deletion; the DIR still exists and + Telnyx is completing the removal (de-registration and cleanup). A verified DIR + keeps serving its branded identity, and keeps billing, until the removal + finishes. """ diff --git a/src/telnyx/types/meeting_session_delete_recording_media_response.py b/src/telnyx/types/meeting_session_delete_recording_media_response.py index 225bf8057..fc16bee28 100644 --- a/src/telnyx/types/meeting_session_delete_recording_media_response.py +++ b/src/telnyx/types/meeting_session_delete_recording_media_response.py @@ -15,7 +15,7 @@ class Data(BaseModel): meeting_session_id: str """The account-scoped Meeting Session identifier.""" - provider: Literal["recall"] + provider: Literal["telnyx"] scope: Literal["provider_recording_media"] diff --git a/src/telnyx/types/meeting_session_retrieve_recordings_response.py b/src/telnyx/types/meeting_session_retrieve_recordings_response.py index e8e18120d..042066968 100644 --- a/src/telnyx/types/meeting_session_retrieve_recordings_response.py +++ b/src/telnyx/types/meeting_session_retrieve_recordings_response.py @@ -11,10 +11,7 @@ class Data(BaseModel): expires_at: Optional[str] = None - """Expiry timestamp when supplied by the provider, or null. - - The current adapter returns null. - """ + """Expiry timestamp when available, or null. Currently returns null.""" type: str diff --git a/src/telnyx/types/model_metadata.py b/src/telnyx/types/model_metadata.py index c4708004b..7246be2cf 100644 --- a/src/telnyx/types/model_metadata.py +++ b/src/telnyx/types/model_metadata.py @@ -86,10 +86,10 @@ class ModelMetadata(BaseModel): """ max_completion_tokens: Optional[int] = None - """Maximum number of completion (output) tokens the model will generate per - request. + """Maximum completion (output) tokens the model may generate per request. - `null` if unconstrained beyond `context_length`. + This value caps the Chat Completions `max_tokens` default and any larger + explicit value on that model. `null` if unconstrained beyond `context_length`. """ object: Optional[str] = None diff --git a/src/telnyx/types/organization_contact.py b/src/telnyx/types/organization_contact.py index 4fe381026..4fd06aa10 100644 --- a/src/telnyx/types/organization_contact.py +++ b/src/telnyx/types/organization_contact.py @@ -9,12 +9,22 @@ class OrganizationContact(BaseModel): email: str + """The email address of the main person Telnyx should contact about this account. + + For a call center (BPO) account this is the email you will verify later, so use + a mailbox you can access. + """ first_name: str + """The first name of the main person Telnyx should contact about this account.""" job_title: str + """The job title of the main person Telnyx should contact about this account.""" last_name: str + """The last name of the main person Telnyx should contact about this account.""" phone_number: str - """E.164 format with leading `+`.""" + """ + The phone number of the main contact, in E.164 format, for example +12125551234. + """ diff --git a/src/telnyx/types/organization_contact_param.py b/src/telnyx/types/organization_contact_param.py index 7948928a4..05cc1dc1d 100644 --- a/src/telnyx/types/organization_contact_param.py +++ b/src/telnyx/types/organization_contact_param.py @@ -9,12 +9,22 @@ class OrganizationContactParam(TypedDict, total=False): email: Required[str] + """The email address of the main person Telnyx should contact about this account. + + For a call center (BPO) account this is the email you will verify later, so use + a mailbox you can access. + """ first_name: Required[str] + """The first name of the main person Telnyx should contact about this account.""" job_title: Required[str] + """The job title of the main person Telnyx should contact about this account.""" last_name: Required[str] + """The last name of the main person Telnyx should contact about this account.""" phone_number: Required[str] - """E.164 format with leading `+`.""" + """ + The phone number of the main contact, in E.164 format, for example +12125551234. + """ diff --git a/src/telnyx/types/physical_address.py b/src/telnyx/types/physical_address.py index 393025227..91dab1c55 100644 --- a/src/telnyx/types/physical_address.py +++ b/src/telnyx/types/physical_address.py @@ -14,12 +14,22 @@ class PhysicalAddress(BaseModel): """State or province code (e.g. `IL`, `ON`).""" city: str + """The city of your registered business address.""" country: str """ISO 3166-1 alpha-2 code (currently `US` or `CA`).""" postal_code: str + """The postal or ZIP code of your registered business address.""" street_address: str + """ + The street address of your registered business, including the building number + and street name. + """ extended_address: Optional[str] = None + """An optional second address line, such as a suite, unit, or floor. + + Leave blank if it does not apply. + """ diff --git a/src/telnyx/types/physical_address_param.py b/src/telnyx/types/physical_address_param.py index b0fd0c4dc..ed987fcbf 100644 --- a/src/telnyx/types/physical_address_param.py +++ b/src/telnyx/types/physical_address_param.py @@ -13,12 +13,22 @@ class PhysicalAddressParam(TypedDict, total=False): """State or province code (e.g. `IL`, `ON`).""" city: Required[str] + """The city of your registered business address.""" country: Required[str] """ISO 3166-1 alpha-2 code (currently `US` or `CA`).""" postal_code: Required[str] + """The postal or ZIP code of your registered business address.""" street_address: Required[str] + """ + The street address of your registered business, including the building number + and street name. + """ extended_address: Optional[str] + """An optional second address line, such as a suite, unit, or floor. + + Leave blank if it does not apply. + """ diff --git a/src/telnyx/types/signature_payload_param.py b/src/telnyx/types/signature_payload_param.py new file mode 100644 index 000000000..ef5f700c4 --- /dev/null +++ b/src/telnyx/types/signature_payload_param.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["SignaturePayloadParam"] + + +class SignaturePayloadParam(TypedDict, total=False): + image_base64: Required[str] + """PNG image, base64-encoded.""" + + signer_name: Optional[str] + """Optional. + + When absent the rendered PDF falls back to the enterprise contact's legal name. + """ diff --git a/src/telnyx/types/spend_limit.py b/src/telnyx/types/spend_limit.py new file mode 100644 index 000000000..2943af686 --- /dev/null +++ b/src/telnyx/types/spend_limit.py @@ -0,0 +1,145 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from datetime import date, datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .spend_limit_period import SpendLimitPeriod + +__all__ = ["SpendLimit", "Block", "Limit", "Evaluation"] + + +class Block(BaseModel): + """The active block of the period. `null` when the period is not blocked.""" + + blocked_until: date + """ + Exclusive end of the block: it is lifted at 00:00 UTC on this date at the + latest. + """ + + detected_at: datetime + """When the block started.""" + + limit_usd: str + """The limit in USD that the spend went above, as a decimal string.""" + + spend_usd: str + """Spend in USD when the block started, as a decimal string.""" + + +class Limit(BaseModel): + """The limit set on the account for the product and period, whoever set it. + + `null` when none is set. + """ + + amount: Optional[str] = None + """Limit in USD, as a decimal string. `null` when `unlimited` is true.""" + + origin: Literal["self_service", "operator"] + """ + `self_service` when a user of the account set it, `operator` when Telnyx support + did. + """ + + unlimited: bool + """True when the limit was set to explicitly no cap.""" + + updated_at: datetime + """When the limit was last set or changed.""" + + +class Evaluation(BaseModel): + """What a create, update or delete did to the period at once. + + Only present in write responses. + """ + + blocked_now: bool + """The change blocked the product: the spend was already above the new limit.""" + + evaluation_deferred: bool + """The spend could not be checked now. + + The change is saved and applied within a few minutes. + """ + + released: bool + """The change lifted a block of this period.""" + + spend_usd: Optional[str] = None + """Spend in USD used for the check, as a decimal string. + + `null` when the spend was not checked. + """ + + still_blocked_other_period: bool + """ + The other period has an active block, so the product stays blocked whatever this + period's result. + """ + + still_over_limit: bool + """A block of this period remains because the spend is still above the new limit.""" + + note: Optional[str] = None + """Additional information about the result, when there is any.""" + + +class SpendLimit(BaseModel): + """The spend limit, spend and block state of one product and period.""" + + block: Optional[Block] = None + """The active block of the period. `null` when the period is not blocked.""" + + blocked: bool + """The product is blocked for this period. + + Always `false` in write responses; list the limits to read the block state. + """ + + effective_limit_usd: Optional[str] = None + """The limit in USD that is enforced, as a decimal string. `null` means unlimited.""" + + limit: Optional[Limit] = None + """The limit set on the account for the product and period, whoever set it. + + `null` when none is set. + """ + + period: SpendLimitPeriod + """`daily` is the current UTC day; `monthly` is the current UTC calendar month.""" + + period_end: date + """Exclusive end of the current period, a UTC date.""" + + period_start: date + """First UTC day of the current period.""" + + product: str + """Product the entry applies to.""" + + product_name: str + """Display name of the product.""" + + record_type: str + """Identifies the type of the resource.""" + + spend_error: Optional[str] = None + """Set when `spend_usd` is `null`.""" + + spend_usd: Optional[str] = None + """Spend in USD so far in the period, as a decimal string. + + It can lag actual usage by about a minute. `null` when it could not be read. + """ + + evaluation: Optional[Evaluation] = None + """What a create, update or delete did to the period at once. + + Only present in write responses. + """ diff --git a/src/telnyx/types/spend_limit_create_params.py b/src/telnyx/types/spend_limit_create_params.py new file mode 100644 index 000000000..3de6cc7a4 --- /dev/null +++ b/src/telnyx/types/spend_limit_create_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .spend_limit_period import SpendLimitPeriod + +__all__ = ["SpendLimitCreateParams", "CreateSpendLimitWithAmount", "CreateSpendLimitUnlimited"] + + +class CreateSpendLimitWithAmount(TypedDict, total=False): + amount: Required[float] + """Limit in USD. `0` blocks at the first cent of spend.""" + + product: Required[str] + """Product to limit, as returned in `product` by the list operation.""" + + period: SpendLimitPeriod + """`daily` is the current UTC day; `monthly` is the current UTC calendar month.""" + + reason: str + """Why the limit is set or changed, kept for audit.""" + + unlimited: Literal[False] + """Optional; only `false` is allowed together with `amount`.""" + + +class CreateSpendLimitUnlimited(TypedDict, total=False): + product: Required[str] + """Product to limit, as returned in `product` by the list operation.""" + + unlimited: Required[Literal[True]] + """`true`: explicitly no cap.""" + + period: SpendLimitPeriod + """`daily` is the current UTC day; `monthly` is the current UTC calendar month.""" + + reason: str + """Why the limit is set or changed, kept for audit.""" + + +SpendLimitCreateParams: TypeAlias = Union[CreateSpendLimitWithAmount, CreateSpendLimitUnlimited] diff --git a/src/telnyx/types/spend_limit_delete_params.py b/src/telnyx/types/spend_limit_delete_params.py new file mode 100644 index 000000000..5f414f5e5 --- /dev/null +++ b/src/telnyx/types/spend_limit_delete_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from .spend_limit_period import SpendLimitPeriod + +__all__ = ["SpendLimitDeleteParams"] + + +class SpendLimitDeleteParams(TypedDict, total=False): + period: SpendLimitPeriod + """Limit period. Defaults to `daily`; send it explicitly.""" + + reason: str + """Why the limit is removed, kept for audit. At most 500 characters.""" diff --git a/src/telnyx/types/spend_limit_list_response.py b/src/telnyx/types/spend_limit_list_response.py new file mode 100644 index 000000000..0dad3a192 --- /dev/null +++ b/src/telnyx/types/spend_limit_list_response.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel +from .spend_limit import SpendLimit + +__all__ = ["SpendLimitListResponse", "Meta"] + + +class Meta(BaseModel): + page_number: Optional[int] = None + + page_size: Optional[int] = None + + total_pages: Optional[int] = None + + total_results: Optional[int] = None + + +class SpendLimitListResponse(BaseModel): + data: List[SpendLimit] + + meta: Optional[Meta] = None diff --git a/src/telnyx/types/spend_limit_period.py b/src/telnyx/types/spend_limit_period.py new file mode 100644 index 000000000..82ea3a744 --- /dev/null +++ b/src/telnyx/types/spend_limit_period.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["SpendLimitPeriod"] + +SpendLimitPeriod: TypeAlias = Literal["daily", "monthly"] diff --git a/src/telnyx/types/spend_limit_response.py b/src/telnyx/types/spend_limit_response.py new file mode 100644 index 000000000..6d6df8310 --- /dev/null +++ b/src/telnyx/types/spend_limit_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .._models import BaseModel +from .spend_limit import SpendLimit + +__all__ = ["SpendLimitResponse"] + + +class SpendLimitResponse(BaseModel): + data: SpendLimit + """The spend limit, spend and block state of one product and period.""" diff --git a/src/telnyx/types/spend_limit_update_params.py b/src/telnyx/types/spend_limit_update_params.py new file mode 100644 index 000000000..1667d9497 --- /dev/null +++ b/src/telnyx/types/spend_limit_update_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .spend_limit_period import SpendLimitPeriod + +__all__ = ["SpendLimitUpdateParams", "UpdateSpendLimitWithAmount", "UpdateSpendLimitUnlimited"] + + +class UpdateSpendLimitWithAmount(TypedDict, total=False): + amount: Required[float] + """Limit in USD. `0` blocks at the first cent of spend.""" + + period: SpendLimitPeriod + """Limit period. Defaults to `daily`; send it explicitly.""" + + reason: str + """Why the limit is set or changed, kept for audit.""" + + unlimited: Literal[False] + """Optional; only `false` is allowed together with `amount`.""" + + +class UpdateSpendLimitUnlimited(TypedDict, total=False): + unlimited: Required[Literal[True]] + """`true`: explicitly no cap.""" + + period: SpendLimitPeriod + """Limit period. Defaults to `daily`; send it explicitly.""" + + reason: str + """Why the limit is set or changed, kept for audit.""" + + +SpendLimitUpdateParams: TypeAlias = Union[UpdateSpendLimitWithAmount, UpdateSpendLimitUnlimited] diff --git a/src/telnyx/types/terms_of_service/tos_agreement.py b/src/telnyx/types/terms_of_service/tos_agreement.py index f644d0d2d..658c61e96 100644 --- a/src/telnyx/types/terms_of_service/tos_agreement.py +++ b/src/telnyx/types/terms_of_service/tos_agreement.py @@ -18,15 +18,19 @@ class TosAgreement(BaseModel): """ id: Optional[str] = None + """The unique identifier of this recorded agreement.""" agreed_at: Optional[datetime] = None + """When you accepted this version of the terms.""" created_at: Optional[datetime] = None + """When this agreement record was created.""" product_type: Optional[TosProductType] = None """Telnyx product the Terms of Service apply to.""" terms_version: Optional[str] = None + """The version of the terms you accepted.""" version: Optional[str] = None """Convenience alias of `terms_version`. Both keys are present on every response.""" diff --git a/src/telnyx/types/terms_of_service_retrieve_info_response.py b/src/telnyx/types/terms_of_service_retrieve_info_response.py index c899bc41f..0a801c9cf 100644 --- a/src/telnyx/types/terms_of_service_retrieve_info_response.py +++ b/src/telnyx/types/terms_of_service_retrieve_info_response.py @@ -13,15 +13,19 @@ class Agreement(BaseModel): current_version: Optional[str] = None + """The latest published version of these terms.""" description: Optional[str] = None + """A short summary of the product these terms cover.""" effective_date: Optional[date] = None + """The date this version took effect.""" product_type: Optional[TosProductType] = None """Telnyx product the Terms of Service apply to.""" terms_url: Optional[str] = None + """A link to the full terms text.""" class TermsOfServiceRetrieveInfoResponse(BaseModel): diff --git a/src/telnyx/types/terms_of_service_retrieve_status_response.py b/src/telnyx/types/terms_of_service_retrieve_status_response.py index aeefc195c..905e51cd5 100644 --- a/src/telnyx/types/terms_of_service_retrieve_status_response.py +++ b/src/telnyx/types/terms_of_service_retrieve_status_response.py @@ -33,6 +33,7 @@ class Data(BaseModel): """Telnyx product the Terms of Service apply to.""" agreed_at: Optional[datetime] = None + """When you accepted the terms, or null if you have not.""" agreed_version: Optional[str] = None """ diff --git a/tests/api_resources/ai/assistants/test_versions.py b/tests/api_resources/ai/assistants/test_versions.py index a101b32ee..a40114c89 100644 --- a/tests/api_resources/ai/assistants/test_versions.py +++ b/tests/api_resources/ai/assistants/test_versions.py @@ -281,6 +281,23 @@ def test_method_update_with_all_params(self, client: Telnyx) -> None: }, ], }, + delegation_settings={ + "enabled": True, + "external_llm": { + "base_url": "base_url", + "model": "model", + "authentication_method": "token", + "certificate_ref": "certificate_ref", + "forward_metadata": True, + "llm_api_key_ref": "llm_api_key_ref", + "token_retrieval_url": "token_retrieval_url", + }, + "instructions": "instructions", + "llm_api_key_ref": "llm_api_key_ref", + "mode": "telnyx", + "model": "model", + "speak_results": True, + }, description="description", dynamic_variables={"foo": "bar"}, dynamic_variables_webhook_timeout_ms=1, @@ -445,6 +462,11 @@ def test_method_update_with_all_params(self, client: Telnyx) -> None: "use_speaker_boost": True, "voice_speed": 0, }, + websocket_settings={ + "auth_ref": "auth_ref", + "enabled": True, + "url": "url", + }, widget_settings={ "agent_thinking_text": "agent_thinking_text", "audio_visualizer_config": { @@ -920,6 +942,23 @@ async def test_method_update_with_all_params(self, async_client: AsyncTelnyx) -> }, ], }, + delegation_settings={ + "enabled": True, + "external_llm": { + "base_url": "base_url", + "model": "model", + "authentication_method": "token", + "certificate_ref": "certificate_ref", + "forward_metadata": True, + "llm_api_key_ref": "llm_api_key_ref", + "token_retrieval_url": "token_retrieval_url", + }, + "instructions": "instructions", + "llm_api_key_ref": "llm_api_key_ref", + "mode": "telnyx", + "model": "model", + "speak_results": True, + }, description="description", dynamic_variables={"foo": "bar"}, dynamic_variables_webhook_timeout_ms=1, @@ -1084,6 +1123,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncTelnyx) -> "use_speaker_boost": True, "voice_speed": 0, }, + websocket_settings={ + "auth_ref": "auth_ref", + "enabled": True, + "url": "url", + }, widget_settings={ "agent_thinking_text": "agent_thinking_text", "audio_visualizer_config": { diff --git a/tests/api_resources/ai/memory/test_namespaces.py b/tests/api_resources/ai/memory/test_namespaces.py index e068a6e61..f9b4174bf 100644 --- a/tests/api_resources/ai/memory/test_namespaces.py +++ b/tests/api_resources/ai/memory/test_namespaces.py @@ -9,7 +9,11 @@ from telnyx import Telnyx, AsyncTelnyx from tests.utils import assert_matches_type -from telnyx.types.ai.memory import NamespaceRetrieveResponse +from telnyx.types.ai.memory import ( + NamespaceListResponse, + NamespaceCreateResponse, + NamespaceRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +21,40 @@ class TestNamespaces: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Telnyx) -> None: + namespace = client.ai.memory.namespaces.create( + name="staging", + ) + assert_matches_type(NamespaceCreateResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Telnyx) -> None: + response = client.ai.memory.namespaces.with_raw_response.create( + name="staging", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + namespace = response.parse() + assert_matches_type(NamespaceCreateResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Telnyx) -> None: + with client.ai.memory.namespaces.with_streaming_response.create( + name="staging", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + namespace = response.parse() + assert_matches_type(NamespaceCreateResponse, namespace, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: Telnyx) -> None: @@ -69,12 +107,116 @@ def test_path_params_retrieve(self, client: Telnyx) -> None: namespace="namespace", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Telnyx) -> None: + namespace = client.ai.memory.namespaces.list() + assert_matches_type(NamespaceListResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Telnyx) -> None: + response = client.ai.memory.namespaces.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + namespace = response.parse() + assert_matches_type(NamespaceListResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Telnyx) -> None: + with client.ai.memory.namespaces.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + namespace = response.parse() + assert_matches_type(NamespaceListResponse, namespace, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: Telnyx) -> None: + namespace = client.ai.memory.namespaces.delete( + "namespace", + ) + assert namespace is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Telnyx) -> None: + response = client.ai.memory.namespaces.with_raw_response.delete( + "namespace", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + namespace = response.parse() + assert namespace is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Telnyx) -> None: + with client.ai.memory.namespaces.with_streaming_response.delete( + "namespace", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + namespace = response.parse() + assert namespace is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `namespace` but received ''"): + client.ai.memory.namespaces.with_raw_response.delete( + "", + ) + class TestAsyncNamespaces: parametrize = pytest.mark.parametrize( "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncTelnyx) -> None: + namespace = await async_client.ai.memory.namespaces.create( + name="staging", + ) + assert_matches_type(NamespaceCreateResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncTelnyx) -> None: + response = await async_client.ai.memory.namespaces.with_raw_response.create( + name="staging", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + namespace = await response.parse() + assert_matches_type(NamespaceCreateResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncTelnyx) -> None: + async with async_client.ai.memory.namespaces.with_streaming_response.create( + name="staging", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + namespace = await response.parse() + assert_matches_type(NamespaceCreateResponse, namespace, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncTelnyx) -> None: @@ -126,3 +268,73 @@ async def test_path_params_retrieve(self, async_client: AsyncTelnyx) -> None: operation_id="", namespace="namespace", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncTelnyx) -> None: + namespace = await async_client.ai.memory.namespaces.list() + assert_matches_type(NamespaceListResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncTelnyx) -> None: + response = await async_client.ai.memory.namespaces.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + namespace = await response.parse() + assert_matches_type(NamespaceListResponse, namespace, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncTelnyx) -> None: + async with async_client.ai.memory.namespaces.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + namespace = await response.parse() + assert_matches_type(NamespaceListResponse, namespace, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncTelnyx) -> None: + namespace = await async_client.ai.memory.namespaces.delete( + "namespace", + ) + assert namespace is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncTelnyx) -> None: + response = await async_client.ai.memory.namespaces.with_raw_response.delete( + "namespace", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + namespace = await response.parse() + assert namespace is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncTelnyx) -> None: + async with async_client.ai.memory.namespaces.with_streaming_response.delete( + "namespace", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + namespace = await response.parse() + assert namespace is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `namespace` but received ''"): + await async_client.ai.memory.namespaces.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/ai/test_assistants.py b/tests/api_resources/ai/test_assistants.py index 025c2fc27..2af148bc7 100644 --- a/tests/api_resources/ai/test_assistants.py +++ b/tests/api_resources/ai/test_assistants.py @@ -15,6 +15,7 @@ AssistantChatResponse, AssistantDeleteResponse, AssistantSendSMSResponse, + AssistantWhatsappResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -222,6 +223,23 @@ def test_method_create_with_all_params(self, client: Telnyx) -> None: }, ], }, + delegation_settings={ + "enabled": True, + "external_llm": { + "base_url": "base_url", + "model": "model", + "authentication_method": "token", + "certificate_ref": "certificate_ref", + "forward_metadata": True, + "llm_api_key_ref": "llm_api_key_ref", + "token_retrieval_url": "token_retrieval_url", + }, + "instructions": "instructions", + "llm_api_key_ref": "llm_api_key_ref", + "mode": "telnyx", + "model": "model", + "speak_results": True, + }, description="description", dynamic_variables={"foo": "bar"}, dynamic_variables_webhook_timeout_ms=1, @@ -383,6 +401,11 @@ def test_method_create_with_all_params(self, client: Telnyx) -> None: "use_speaker_boost": True, "voice_speed": 0, }, + websocket_settings={ + "auth_ref": "auth_ref", + "enabled": True, + "url": "url", + }, widget_settings={ "agent_thinking_text": "agent_thinking_text", "audio_visualizer_config": { @@ -682,6 +705,23 @@ def test_method_update_with_all_params(self, client: Telnyx) -> None: }, ], }, + delegation_settings={ + "enabled": True, + "external_llm": { + "base_url": "base_url", + "model": "model", + "authentication_method": "token", + "certificate_ref": "certificate_ref", + "forward_metadata": True, + "llm_api_key_ref": "llm_api_key_ref", + "token_retrieval_url": "token_retrieval_url", + }, + "instructions": "instructions", + "llm_api_key_ref": "llm_api_key_ref", + "mode": "telnyx", + "model": "model", + "speak_results": True, + }, description="description", dynamic_variables={"foo": "bar"}, dynamic_variables_webhook_timeout_ms=1, @@ -847,6 +887,11 @@ def test_method_update_with_all_params(self, client: Telnyx) -> None: "use_speaker_boost": True, "voice_speed": 0, }, + websocket_settings={ + "auth_ref": "auth_ref", + "enabled": True, + "url": "url", + }, widget_settings={ "agent_thinking_text": "agent_thinking_text", "audio_visualizer_config": { @@ -1237,6 +1282,73 @@ def test_path_params_send_sms(self, client: Telnyx) -> None: to="To", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_whatsapp(self, client: Telnyx) -> None: + assistant = client.ai.assistants.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_whatsapp_with_all_params(self, client: Telnyx) -> None: + assistant = client.ai.assistants.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + conversation_metadata={"order_id": "A1"}, + idempotency_key="8e03978e-40d5-43e8-bc93-6894a57f9326", + ) + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_whatsapp(self, client: Telnyx) -> None: + response = client.ai.assistants.with_raw_response.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + assistant = response.parse() + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_whatsapp(self, client: Telnyx) -> None: + with client.ai.assistants.with_streaming_response.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_whatsapp(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + client.ai.assistants.with_raw_response.whatsapp( + assistant_id="", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) + class TestAsyncAssistants: parametrize = pytest.mark.parametrize( @@ -1442,6 +1554,23 @@ async def test_method_create_with_all_params(self, async_client: AsyncTelnyx) -> }, ], }, + delegation_settings={ + "enabled": True, + "external_llm": { + "base_url": "base_url", + "model": "model", + "authentication_method": "token", + "certificate_ref": "certificate_ref", + "forward_metadata": True, + "llm_api_key_ref": "llm_api_key_ref", + "token_retrieval_url": "token_retrieval_url", + }, + "instructions": "instructions", + "llm_api_key_ref": "llm_api_key_ref", + "mode": "telnyx", + "model": "model", + "speak_results": True, + }, description="description", dynamic_variables={"foo": "bar"}, dynamic_variables_webhook_timeout_ms=1, @@ -1603,6 +1732,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncTelnyx) -> "use_speaker_boost": True, "voice_speed": 0, }, + websocket_settings={ + "auth_ref": "auth_ref", + "enabled": True, + "url": "url", + }, widget_settings={ "agent_thinking_text": "agent_thinking_text", "audio_visualizer_config": { @@ -1902,6 +2036,23 @@ async def test_method_update_with_all_params(self, async_client: AsyncTelnyx) -> }, ], }, + delegation_settings={ + "enabled": True, + "external_llm": { + "base_url": "base_url", + "model": "model", + "authentication_method": "token", + "certificate_ref": "certificate_ref", + "forward_metadata": True, + "llm_api_key_ref": "llm_api_key_ref", + "token_retrieval_url": "token_retrieval_url", + }, + "instructions": "instructions", + "llm_api_key_ref": "llm_api_key_ref", + "mode": "telnyx", + "model": "model", + "speak_results": True, + }, description="description", dynamic_variables={"foo": "bar"}, dynamic_variables_webhook_timeout_ms=1, @@ -2067,6 +2218,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncTelnyx) -> "use_speaker_boost": True, "voice_speed": 0, }, + websocket_settings={ + "auth_ref": "auth_ref", + "enabled": True, + "url": "url", + }, widget_settings={ "agent_thinking_text": "agent_thinking_text", "audio_visualizer_config": { @@ -2456,3 +2612,70 @@ async def test_path_params_send_sms(self, async_client: AsyncTelnyx) -> None: from_="From", to="To", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_whatsapp(self, async_client: AsyncTelnyx) -> None: + assistant = await async_client.ai.assistants.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_whatsapp_with_all_params(self, async_client: AsyncTelnyx) -> None: + assistant = await async_client.ai.assistants.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + conversation_metadata={"order_id": "A1"}, + idempotency_key="8e03978e-40d5-43e8-bc93-6894a57f9326", + ) + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_whatsapp(self, async_client: AsyncTelnyx) -> None: + response = await async_client.ai.assistants.with_raw_response.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + assistant = await response.parse() + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_whatsapp(self, async_client: AsyncTelnyx) -> None: + async with async_client.ai.assistants.with_streaming_response.whatsapp( + assistant_id="assistant_id", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(AssistantWhatsappResponse, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_whatsapp(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + await async_client.ai.assistants.with_raw_response.whatsapp( + assistant_id="", + content="Send the login verification code 482913 to the customer.", + from_="+13125550001", + to="+13125550002", + ) diff --git a/tests/api_resources/ai/test_embeddings.py b/tests/api_resources/ai/test_embeddings.py index b68a178d7..07a45f464 100644 --- a/tests/api_resources/ai/test_embeddings.py +++ b/tests/api_resources/ai/test_embeddings.py @@ -37,7 +37,7 @@ def test_method_create_with_all_params(self, client: Telnyx) -> None: bucket_name="Bucket Name", document_chunk_overlap_size=512, document_chunk_size=1024, - embedding_model="thenlper/gte-large", + embedding_model="intfloat/multilingual-e5-large", loader="default", idempotency_key="8e03978e-40d5-43e8-bc93-6894a57f9326", ) @@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncTelnyx) -> bucket_name="Bucket Name", document_chunk_overlap_size=512, document_chunk_size=1024, - embedding_model="thenlper/gte-large", + embedding_model="intfloat/multilingual-e5-large", loader="default", idempotency_key="8e03978e-40d5-43e8-bc93-6894a57f9326", ) diff --git a/tests/api_resources/enterprises/test_dir.py b/tests/api_resources/enterprises/test_dir.py index 79e7877b2..b6cca8c3c 100644 --- a/tests/api_resources/enterprises/test_dir.py +++ b/tests/api_resources/enterprises/test_dir.py @@ -47,6 +47,12 @@ def test_method_create_with_all_params(self, client: Telnyx) -> None: certify_ip_ownership=True, certify_no_shaft_content=True, display_name="Acme Plumbing", + bpo_authorizations=[ + { + "bpo_enterprise_id": "4a6192a4-573d-446d-b3ce-aff9117272a6", + "loa_document_id": "2a7e8337-e803-4057-a4ae-26c40eb0bc6c", + } + ], documents=[ { "document_id": "2a7e8337-e803-4057-a4ae-26c40eb0bc6c", @@ -56,6 +62,7 @@ def test_method_create_with_all_params(self, client: Telnyx) -> None: ], logo_url="https://acmeplumbing.example.com/logo-256.bmp", reselling=False, + webhook_url="https://mapleridge.example.com/webhooks/branded-calling", ) assert_matches_type(DirWrapped, dir, path=["response"]) @@ -206,6 +213,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncTelnyx) -> certify_ip_ownership=True, certify_no_shaft_content=True, display_name="Acme Plumbing", + bpo_authorizations=[ + { + "bpo_enterprise_id": "4a6192a4-573d-446d-b3ce-aff9117272a6", + "loa_document_id": "2a7e8337-e803-4057-a4ae-26c40eb0bc6c", + } + ], documents=[ { "document_id": "2a7e8337-e803-4057-a4ae-26c40eb0bc6c", @@ -215,6 +228,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncTelnyx) -> ], logo_url="https://acmeplumbing.example.com/logo-256.bmp", reselling=False, + webhook_url="https://mapleridge.example.com/webhooks/branded-calling", ) assert_matches_type(DirWrapped, dir, path=["response"]) diff --git a/tests/api_resources/enterprises/test_verify_email.py b/tests/api_resources/enterprises/test_verify_email.py new file mode 100644 index 000000000..4b3ea3010 --- /dev/null +++ b/tests/api_resources/enterprises/test_verify_email.py @@ -0,0 +1,200 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from telnyx import Telnyx, AsyncTelnyx +from tests.utils import assert_matches_type +from telnyx.types.enterprises import EnterpriseEmailVerificationStatusWrapped + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestVerifyEmail: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Telnyx) -> None: + verify_email = client.enterprises.verify_email.create( + "4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Telnyx) -> None: + response = client.enterprises.verify_email.with_raw_response.create( + "4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verify_email = response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Telnyx) -> None: + with client.enterprises.verify_email.with_streaming_response.create( + "4a6192a4-573d-446d-b3ce-aff9117272a6", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verify_email = response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_create(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `enterprise_id` but received ''"): + client.enterprises.verify_email.with_raw_response.create( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_confirm(self, client: Telnyx) -> None: + verify_email = client.enterprises.verify_email.confirm( + enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + code="482915", + ) + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_confirm(self, client: Telnyx) -> None: + response = client.enterprises.verify_email.with_raw_response.confirm( + enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + code="482915", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verify_email = response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_confirm(self, client: Telnyx) -> None: + with client.enterprises.verify_email.with_streaming_response.confirm( + enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + code="482915", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verify_email = response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_confirm(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `enterprise_id` but received ''"): + client.enterprises.verify_email.with_raw_response.confirm( + enterprise_id="", + code="482915", + ) + + +class TestAsyncVerifyEmail: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncTelnyx) -> None: + verify_email = await async_client.enterprises.verify_email.create( + "4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncTelnyx) -> None: + response = await async_client.enterprises.verify_email.with_raw_response.create( + "4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verify_email = await response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncTelnyx) -> None: + async with async_client.enterprises.verify_email.with_streaming_response.create( + "4a6192a4-573d-446d-b3ce-aff9117272a6", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verify_email = await response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_create(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `enterprise_id` but received ''"): + await async_client.enterprises.verify_email.with_raw_response.create( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_confirm(self, async_client: AsyncTelnyx) -> None: + verify_email = await async_client.enterprises.verify_email.confirm( + enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + code="482915", + ) + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_confirm(self, async_client: AsyncTelnyx) -> None: + response = await async_client.enterprises.verify_email.with_raw_response.confirm( + enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + code="482915", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verify_email = await response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_confirm(self, async_client: AsyncTelnyx) -> None: + async with async_client.enterprises.verify_email.with_streaming_response.confirm( + enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + code="482915", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verify_email = await response.parse() + assert_matches_type(EnterpriseEmailVerificationStatusWrapped, verify_email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_confirm(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `enterprise_id` but received ''"): + await async_client.enterprises.verify_email.with_raw_response.confirm( + enterprise_id="", + code="482915", + ) diff --git a/tests/api_resources/test_dir.py b/tests/api_resources/test_dir.py index 790d52ec9..003bd7592 100644 --- a/tests/api_resources/test_dir.py +++ b/tests/api_resources/test_dir.py @@ -13,8 +13,10 @@ from tests.utils import assert_matches_type from telnyx.types import ( DirWrapped, + DirDeleteResponse, InfringementClaim, DirListDocumentTypesResponse, + DirRetrieveBpoAuthorizationsResponse, ) from telnyx._utils import parse_datetime from telnyx._response import ( @@ -89,6 +91,12 @@ def test_method_update_with_all_params(self, client: Telnyx) -> None: dir_id="16635d38-75a6-4481-82e8-69af60e05011", authorizer_email="dev@stainless.com", authorizer_name="authorizer_name", + bpo_authorizations=[ + { + "bpo_enterprise_id": "4a6192a4-573d-446d-b3ce-aff9117272a6", + "loa_document_id": "2a7e8337-e803-4057-a4ae-26c40eb0bc6c", + } + ], call_reasons=["Appointment reminders", "Billing inquiries", "Lab results"], certify_brand_is_accurate=True, certify_ip_ownership=True, @@ -103,6 +111,7 @@ def test_method_update_with_all_params(self, client: Telnyx) -> None: ], logo_url="https://acmeplumbing.example.com/logo-v2-256.bmp", reselling=True, + webhook_url="https://mapleridge.example.com/webhooks/branded-calling", ) assert_matches_type(DirWrapped, dir, path=["response"]) @@ -190,7 +199,7 @@ def test_method_delete(self, client: Telnyx) -> None: dir = client.dir.delete( "16635d38-75a6-4481-82e8-69af60e05011", ) - assert dir is None + assert_matches_type(DirDeleteResponse, dir, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -202,7 +211,7 @@ def test_raw_response_delete(self, client: Telnyx) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" dir = response.parse() - assert dir is None + assert_matches_type(DirDeleteResponse, dir, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -214,7 +223,7 @@ def test_streaming_response_delete(self, client: Telnyx) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" dir = response.parse() - assert dir is None + assert_matches_type(DirDeleteResponse, dir, path=["response"]) assert cast(Any, response.is_closed) is True @@ -226,6 +235,85 @@ def test_path_params_delete(self, client: Telnyx) -> None: "", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_bpo_loa(self, client: Telnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + dir = client.dir.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + assert dir.is_closed + assert dir.json() == {"foo": "bar"} + assert cast(Any, dir.is_closed) is True + assert isinstance(dir, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_bpo_loa_with_all_params(self, client: Telnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + dir = client.dir.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + signature={ + "image_base64": "x", + "signer_name": "signer_name", + }, + ) + assert dir.is_closed + assert dir.json() == {"foo": "bar"} + assert cast(Any, dir.is_closed) is True + assert isinstance(dir, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_bpo_loa(self, client: Telnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + dir = client.dir.with_raw_response.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + + assert dir.is_closed is True + assert dir.http_request.headers.get("X-Stainless-Lang") == "python" + assert dir.json() == {"foo": "bar"} + assert isinstance(dir, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_bpo_loa(self, client: Telnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + with client.dir.with_streaming_response.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) as dir: + assert not dir.is_closed + assert dir.http_request.headers.get("X-Stainless-Lang") == "python" + + assert dir.json() == {"foo": "bar"} + assert cast(Any, dir.is_closed) is True + assert isinstance(dir, StreamedBinaryAPIResponse) + + assert cast(Any, dir.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_bpo_loa(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `dir_id` but received ''"): + client.dir.with_raw_response.bpo_loa( + dir_id="", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_document_types(self, client: Telnyx) -> None: @@ -399,6 +487,58 @@ def test_path_params_new_loa(self, client: Telnyx) -> None: phone_numbers=["+13125550000"], ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_bpo_authorizations(self, client: Telnyx) -> None: + dir = client.dir.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + ) + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_bpo_authorizations_with_all_params(self, client: Telnyx) -> None: + dir = client.dir.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + page_number=1, + page_size=20, + ) + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve_bpo_authorizations(self, client: Telnyx) -> None: + response = client.dir.with_raw_response.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dir = response.parse() + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve_bpo_authorizations(self, client: Telnyx) -> None: + with client.dir.with_streaming_response.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dir = response.parse() + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve_bpo_authorizations(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `dir_id` but received ''"): + client.dir.with_raw_response.retrieve_bpo_authorizations( + dir_id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_submit(self, client: Telnyx) -> None: @@ -589,6 +729,12 @@ async def test_method_update_with_all_params(self, async_client: AsyncTelnyx) -> dir_id="16635d38-75a6-4481-82e8-69af60e05011", authorizer_email="dev@stainless.com", authorizer_name="authorizer_name", + bpo_authorizations=[ + { + "bpo_enterprise_id": "4a6192a4-573d-446d-b3ce-aff9117272a6", + "loa_document_id": "2a7e8337-e803-4057-a4ae-26c40eb0bc6c", + } + ], call_reasons=["Appointment reminders", "Billing inquiries", "Lab results"], certify_brand_is_accurate=True, certify_ip_ownership=True, @@ -603,6 +749,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncTelnyx) -> ], logo_url="https://acmeplumbing.example.com/logo-v2-256.bmp", reselling=True, + webhook_url="https://mapleridge.example.com/webhooks/branded-calling", ) assert_matches_type(DirWrapped, dir, path=["response"]) @@ -690,7 +837,7 @@ async def test_method_delete(self, async_client: AsyncTelnyx) -> None: dir = await async_client.dir.delete( "16635d38-75a6-4481-82e8-69af60e05011", ) - assert dir is None + assert_matches_type(DirDeleteResponse, dir, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -702,7 +849,7 @@ async def test_raw_response_delete(self, async_client: AsyncTelnyx) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" dir = await response.parse() - assert dir is None + assert_matches_type(DirDeleteResponse, dir, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -714,7 +861,7 @@ async def test_streaming_response_delete(self, async_client: AsyncTelnyx) -> Non assert response.http_request.headers.get("X-Stainless-Lang") == "python" dir = await response.parse() - assert dir is None + assert_matches_type(DirDeleteResponse, dir, path=["response"]) assert cast(Any, response.is_closed) is True @@ -726,6 +873,85 @@ async def test_path_params_delete(self, async_client: AsyncTelnyx) -> None: "", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_bpo_loa(self, async_client: AsyncTelnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + dir = await async_client.dir.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + assert dir.is_closed + assert await dir.json() == {"foo": "bar"} + assert cast(Any, dir.is_closed) is True + assert isinstance(dir, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_bpo_loa_with_all_params(self, async_client: AsyncTelnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + dir = await async_client.dir.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + signature={ + "image_base64": "x", + "signer_name": "signer_name", + }, + ) + assert dir.is_closed + assert await dir.json() == {"foo": "bar"} + assert cast(Any, dir.is_closed) is True + assert isinstance(dir, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_bpo_loa(self, async_client: AsyncTelnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + dir = await async_client.dir.with_raw_response.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + + assert dir.is_closed is True + assert dir.http_request.headers.get("X-Stainless-Lang") == "python" + assert await dir.json() == {"foo": "bar"} + assert isinstance(dir, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_bpo_loa(self, async_client: AsyncTelnyx, respx_mock: MockRouter) -> None: + respx_mock.post("/dir/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/bpo_loa").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + async with async_client.dir.with_streaming_response.bpo_loa( + dir_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) as dir: + assert not dir.is_closed + assert dir.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await dir.json() == {"foo": "bar"} + assert cast(Any, dir.is_closed) is True + assert isinstance(dir, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, dir.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_bpo_loa(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `dir_id` but received ''"): + await async_client.dir.with_raw_response.bpo_loa( + dir_id="", + bpo_enterprise_id="4a6192a4-573d-446d-b3ce-aff9117272a6", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_document_types(self, async_client: AsyncTelnyx) -> None: @@ -899,6 +1125,58 @@ async def test_path_params_new_loa(self, async_client: AsyncTelnyx) -> None: phone_numbers=["+13125550000"], ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_bpo_authorizations(self, async_client: AsyncTelnyx) -> None: + dir = await async_client.dir.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + ) + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_bpo_authorizations_with_all_params(self, async_client: AsyncTelnyx) -> None: + dir = await async_client.dir.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + page_number=1, + page_size=20, + ) + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve_bpo_authorizations(self, async_client: AsyncTelnyx) -> None: + response = await async_client.dir.with_raw_response.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dir = await response.parse() + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve_bpo_authorizations(self, async_client: AsyncTelnyx) -> None: + async with async_client.dir.with_streaming_response.retrieve_bpo_authorizations( + dir_id="16635d38-75a6-4481-82e8-69af60e05011", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dir = await response.parse() + assert_matches_type(DirRetrieveBpoAuthorizationsResponse, dir, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve_bpo_authorizations(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `dir_id` but received ''"): + await async_client.dir.with_raw_response.retrieve_bpo_authorizations( + dir_id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_submit(self, async_client: AsyncTelnyx) -> None: diff --git a/tests/api_resources/test_enterprises.py b/tests/api_resources/test_enterprises.py index 94d61dbb0..de481ecad 100644 --- a/tests/api_resources/test_enterprises.py +++ b/tests/api_resources/test_enterprises.py @@ -360,6 +360,7 @@ def test_method_list(self, client: Telnyx) -> None: def test_method_list_with_all_params(self, client: Telnyx) -> None: enterprise = client.enterprises.list( filter_legal_name_contains="Acme", + filter_role_type="bpo", legal_name="Acme", page_number=1, page_size=10, @@ -817,6 +818,7 @@ async def test_method_list(self, async_client: AsyncTelnyx) -> None: async def test_method_list_with_all_params(self, async_client: AsyncTelnyx) -> None: enterprise = await async_client.enterprises.list( filter_legal_name_contains="Acme", + filter_role_type="bpo", legal_name="Acme", page_number=1, page_size=10, diff --git a/tests/api_resources/test_spend_limits.py b/tests/api_resources/test_spend_limits.py new file mode 100644 index 000000000..df6fc03e6 --- /dev/null +++ b/tests/api_resources/test_spend_limits.py @@ -0,0 +1,611 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from telnyx import Telnyx, AsyncTelnyx +from tests.utils import assert_matches_type +from telnyx.types import ( + SpendLimitResponse, + SpendLimitListResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSpendLimits: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_overload_1(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.create( + amount=100, + product="inference", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params_overload_1(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.create( + amount=100, + product="inference", + period="daily", + reason="Team budget", + unlimited=False, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create_overload_1(self, client: Telnyx) -> None: + response = client.spend_limits.with_raw_response.create( + amount=100, + product="inference", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create_overload_1(self, client: Telnyx) -> None: + with client.spend_limits.with_streaming_response.create( + amount=100, + product="inference", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_overload_2(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.create( + product="inference", + unlimited=True, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params_overload_2(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.create( + product="inference", + unlimited=True, + period="daily", + reason="Team budget", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create_overload_2(self, client: Telnyx) -> None: + response = client.spend_limits.with_raw_response.create( + product="inference", + unlimited=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create_overload_2(self, client: Telnyx) -> None: + with client.spend_limits.with_streaming_response.create( + product="inference", + unlimited=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_overload_1(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.update( + product="inference", + amount=100, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params_overload_1(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.update( + product="inference", + amount=100, + period="daily", + reason="Raised for the product launch", + unlimited=False, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update_overload_1(self, client: Telnyx) -> None: + response = client.spend_limits.with_raw_response.update( + product="inference", + amount=100, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update_overload_1(self, client: Telnyx) -> None: + with client.spend_limits.with_streaming_response.update( + product="inference", + amount=100, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update_overload_1(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `product` but received ''"): + client.spend_limits.with_raw_response.update( + product="", + amount=100, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_overload_2(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.update( + product="inference", + unlimited=True, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params_overload_2(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.update( + product="inference", + unlimited=True, + period="daily", + reason="Raised for the product launch", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update_overload_2(self, client: Telnyx) -> None: + response = client.spend_limits.with_raw_response.update( + product="inference", + unlimited=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update_overload_2(self, client: Telnyx) -> None: + with client.spend_limits.with_streaming_response.update( + product="inference", + unlimited=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update_overload_2(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `product` but received ''"): + client.spend_limits.with_raw_response.update( + product="", + unlimited=True, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.list() + assert_matches_type(SpendLimitListResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Telnyx) -> None: + response = client.spend_limits.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = response.parse() + assert_matches_type(SpendLimitListResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Telnyx) -> None: + with client.spend_limits.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = response.parse() + assert_matches_type(SpendLimitListResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.delete( + product="inference", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete_with_all_params(self, client: Telnyx) -> None: + spend_limit = client.spend_limits.delete( + product="inference", + period="daily", + reason="reason", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Telnyx) -> None: + response = client.spend_limits.with_raw_response.delete( + product="inference", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Telnyx) -> None: + with client.spend_limits.with_streaming_response.delete( + product="inference", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: Telnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `product` but received ''"): + client.spend_limits.with_raw_response.delete( + product="", + ) + + +class TestAsyncSpendLimits: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_overload_1(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.create( + amount=100, + product="inference", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.create( + amount=100, + product="inference", + period="daily", + reason="Team budget", + unlimited=False, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create_overload_1(self, async_client: AsyncTelnyx) -> None: + response = await async_client.spend_limits.with_raw_response.create( + amount=100, + product="inference", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create_overload_1(self, async_client: AsyncTelnyx) -> None: + async with async_client.spend_limits.with_streaming_response.create( + amount=100, + product="inference", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.create( + product="inference", + unlimited=True, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.create( + product="inference", + unlimited=True, + period="daily", + reason="Team budget", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncTelnyx) -> None: + response = await async_client.spend_limits.with_raw_response.create( + product="inference", + unlimited=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncTelnyx) -> None: + async with async_client.spend_limits.with_streaming_response.create( + product="inference", + unlimited=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_overload_1(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.update( + product="inference", + amount=100, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params_overload_1(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.update( + product="inference", + amount=100, + period="daily", + reason="Raised for the product launch", + unlimited=False, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update_overload_1(self, async_client: AsyncTelnyx) -> None: + response = await async_client.spend_limits.with_raw_response.update( + product="inference", + amount=100, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update_overload_1(self, async_client: AsyncTelnyx) -> None: + async with async_client.spend_limits.with_streaming_response.update( + product="inference", + amount=100, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_update_overload_1(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `product` but received ''"): + await async_client.spend_limits.with_raw_response.update( + product="", + amount=100, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_overload_2(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.update( + product="inference", + unlimited=True, + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params_overload_2(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.update( + product="inference", + unlimited=True, + period="daily", + reason="Raised for the product launch", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update_overload_2(self, async_client: AsyncTelnyx) -> None: + response = await async_client.spend_limits.with_raw_response.update( + product="inference", + unlimited=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update_overload_2(self, async_client: AsyncTelnyx) -> None: + async with async_client.spend_limits.with_streaming_response.update( + product="inference", + unlimited=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_update_overload_2(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `product` but received ''"): + await async_client.spend_limits.with_raw_response.update( + product="", + unlimited=True, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.list() + assert_matches_type(SpendLimitListResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncTelnyx) -> None: + response = await async_client.spend_limits.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = await response.parse() + assert_matches_type(SpendLimitListResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncTelnyx) -> None: + async with async_client.spend_limits.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = await response.parse() + assert_matches_type(SpendLimitListResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.delete( + product="inference", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete_with_all_params(self, async_client: AsyncTelnyx) -> None: + spend_limit = await async_client.spend_limits.delete( + product="inference", + period="daily", + reason="reason", + ) + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncTelnyx) -> None: + response = await async_client.spend_limits.with_raw_response.delete( + product="inference", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncTelnyx) -> None: + async with async_client.spend_limits.with_streaming_response.delete( + product="inference", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_limit = await response.parse() + assert_matches_type(SpendLimitResponse, spend_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncTelnyx) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `product` but received ''"): + await async_client.spend_limits.with_raw_response.delete( + product="", + )