From 0bb8149feb704a11a35ed59477b789c47e508aa4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 1 Oct 2026 18:42:51 +0000
Subject: [PATCH] chore: sync code from next into release PR
---
.release-please-manifest.json | 2 +-
.stats.yml | 2 +-
CHANGELOG.md | 7 +
api.md | 59 +-
pyproject.toml | 2 +-
src/telnyx/_client.py | 98 +++
src/telnyx/_version.py | 2 +-
src/telnyx/resources/__init__.py | 14 +
.../resources/ai/assistants/assistants.py | 238 ++++++
.../resources/ai/assistants/versions.py | 38 +
src/telnyx/resources/ai/memory/memory.py | 6 -
.../ai/memory/namespaces/namespaces.py | 233 ++++-
src/telnyx/resources/ai/openai/chat.py | 20 +-
src/telnyx/resources/calls/actions.py | 16 +-
src/telnyx/resources/dir/dir.py | 384 ++++++++-
src/telnyx/resources/dir/phone_numbers.py | 22 +-
src/telnyx/resources/dir/references.py | 30 +-
src/telnyx/resources/enterprises/__init__.py | 14 +
src/telnyx/resources/enterprises/dir.py | 39 +-
.../resources/enterprises/enterprises.py | 310 ++++++-
.../resources/enterprises/reputation/loa.py | 5 +-
.../resources/enterprises/verify_email.py | 287 +++++++
.../meeting_sessions/meeting_sessions.py | 24 +-
src/telnyx/resources/spend_limits.py | 803 ++++++++++++++++++
src/telnyx/types/__init__.py | 17 +
src/telnyx/types/ai/__init__.py | 19 +
.../types/ai/assistant_create_params.py | 22 +
.../types/ai/assistant_update_params.py | 22 +
.../types/ai/assistant_whatsapp_params.py | 38 +
.../types/ai/assistant_whatsapp_response.py | 15 +
.../ai/assistants/version_update_params.py | 22 +
.../types/ai/conversation_flow_req_param.py | 4 +-
src/telnyx/types/ai/delegation_settings.py | 75 ++
.../types/ai/delegation_settings_param.py | 73 ++
src/telnyx/types/ai/external_llm_param.py | 45 +
src/telnyx/types/ai/flow_edge.py | 33 +-
src/telnyx/types/ai/flow_edge_param.py | 33 +-
src/telnyx/types/ai/flow_node_req_param.py | 2 +-
src/telnyx/types/ai/inference_embedding.py | 22 +
...inference_embedding_webhook_tool_params.py | 14 +-
...nce_embedding_webhook_tool_params_param.py | 14 +-
src/telnyx/types/ai/memory/__init__.py | 17 +
src/telnyx/types/ai/memory/namespace.py | 20 +
.../ai/memory/namespace_create_params.py | 12 +
.../ai/memory/namespace_create_response.py | 13 +
.../ai/memory/namespace_list_response.py | 14 +
.../openai/chat_create_completion_params.py | 13 +-
src/telnyx/types/ai/tool_node.py | 19 +-
src/telnyx/types/ai/tool_node_req_param.py | 24 +-
src/telnyx/types/ai/webhook_tool_param.py | 8 +
src/telnyx/types/ai/websocket_settings.py | 35 +
.../types/ai/websocket_settings_param.py | 33 +
src/telnyx/types/billing_contact.py | 17 +-
src/telnyx/types/billing_contact_param.py | 17 +-
.../types/bpo_authorization_input_param.py | 25 +
.../types/calls/action_reject_params.py | 9 +-
src/telnyx/types/dir/dir.py | 16 +
.../types/dir/phone_number_remove_params.py | 4 +
.../types/dir/reference_update_params.py | 22 +-
src/telnyx/types/dir_bpo_loa_params.py | 26 +
src/telnyx/types/dir_delete_response.py | 23 +
src/telnyx/types/dir_new_loa_params.py | 22 +-
.../dir_retrieve_bpo_authorizations_params.py | 20 +
...ir_retrieve_bpo_authorizations_response.py | 53 ++
src/telnyx/types/dir_status.py | 1 +
.../types/dir_update_infringement_params.py | 9 +-
src/telnyx/types/dir_update_params.py | 21 +-
src/telnyx/types/document.py | 1 +
src/telnyx/types/document_param.py | 1 +
src/telnyx/types/enterprise_create_params.py | 62 +-
src/telnyx/types/enterprise_list_params.py | 8 +-
src/telnyx/types/enterprise_public.py | 85 +-
src/telnyx/types/enterprise_update_params.py | 66 +-
src/telnyx/types/enterprises/__init__.py | 8 +
.../types/enterprises/dir_create_params.py | 25 +-
...prise_email_verification_status_wrapped.py | 41 +
.../reputation/agent_input_param.py | 31 +
.../reputation/loa_render_params.py | 37 +-
.../verify_email_confirm_params.py | 12 +
src/telnyx/types/infringement_claim.py | 4 +
...session_delete_recording_media_response.py | 2 +-
...ng_session_retrieve_recordings_response.py | 5 +-
src/telnyx/types/model_metadata.py | 6 +-
src/telnyx/types/organization_contact.py | 12 +-
.../types/organization_contact_param.py | 12 +-
src/telnyx/types/physical_address.py | 10 +
src/telnyx/types/physical_address_param.py | 10 +
src/telnyx/types/signature_payload_param.py | 19 +
src/telnyx/types/spend_limit.py | 145 ++++
src/telnyx/types/spend_limit_create_params.py | 44 +
src/telnyx/types/spend_limit_delete_params.py | 17 +
src/telnyx/types/spend_limit_list_response.py | 26 +
src/telnyx/types/spend_limit_period.py | 9 +
src/telnyx/types/spend_limit_response.py | 13 +
src/telnyx/types/spend_limit_update_params.py | 38 +
.../types/terms_of_service/tos_agreement.py | 4 +
...terms_of_service_retrieve_info_response.py | 4 +
...rms_of_service_retrieve_status_response.py | 1 +
.../ai/assistants/test_versions.py | 44 +
.../ai/memory/test_namespaces.py | 214 ++++-
tests/api_resources/ai/test_assistants.py | 223 +++++
tests/api_resources/ai/test_embeddings.py | 4 +-
tests/api_resources/enterprises/test_dir.py | 14 +
.../enterprises/test_verify_email.py | 200 +++++
tests/api_resources/test_dir.py | 290 ++++++-
tests/api_resources/test_enterprises.py | 2 +
tests/api_resources/test_spend_limits.py | 611 +++++++++++++
107 files changed, 5734 insertions(+), 244 deletions(-)
create mode 100644 src/telnyx/resources/enterprises/verify_email.py
create mode 100644 src/telnyx/resources/spend_limits.py
create mode 100644 src/telnyx/types/ai/assistant_whatsapp_params.py
create mode 100644 src/telnyx/types/ai/assistant_whatsapp_response.py
create mode 100644 src/telnyx/types/ai/delegation_settings.py
create mode 100644 src/telnyx/types/ai/delegation_settings_param.py
create mode 100644 src/telnyx/types/ai/external_llm_param.py
create mode 100644 src/telnyx/types/ai/memory/namespace.py
create mode 100644 src/telnyx/types/ai/memory/namespace_create_params.py
create mode 100644 src/telnyx/types/ai/memory/namespace_create_response.py
create mode 100644 src/telnyx/types/ai/memory/namespace_list_response.py
create mode 100644 src/telnyx/types/ai/websocket_settings.py
create mode 100644 src/telnyx/types/ai/websocket_settings_param.py
create mode 100644 src/telnyx/types/bpo_authorization_input_param.py
create mode 100644 src/telnyx/types/dir_bpo_loa_params.py
create mode 100644 src/telnyx/types/dir_delete_response.py
create mode 100644 src/telnyx/types/dir_retrieve_bpo_authorizations_params.py
create mode 100644 src/telnyx/types/dir_retrieve_bpo_authorizations_response.py
create mode 100644 src/telnyx/types/enterprises/enterprise_email_verification_status_wrapped.py
create mode 100644 src/telnyx/types/enterprises/verify_email_confirm_params.py
create mode 100644 src/telnyx/types/signature_payload_param.py
create mode 100644 src/telnyx/types/spend_limit.py
create mode 100644 src/telnyx/types/spend_limit_create_params.py
create mode 100644 src/telnyx/types/spend_limit_delete_params.py
create mode 100644 src/telnyx/types/spend_limit_list_response.py
create mode 100644 src/telnyx/types/spend_limit_period.py
create mode 100644 src/telnyx/types/spend_limit_response.py
create mode 100644 src/telnyx/types/spend_limit_update_params.py
create mode 100644 tests/api_resources/enterprises/test_verify_email.py
create mode 100644 tests/api_resources/test_spend_limits.py
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="",
+ )