From 5cded745f3a5a725db610b09a4c456fad434c3a5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:17:40 +0000 Subject: [PATCH 01/13] feat(api): api update --- .stats.yml | 4 +- src/zavudev/resources/senders/agent/agent.py | 24 ++++ src/zavudev/types/senders/agent/agent.py | 116 +++++++++++++++++- .../types/senders/agent_create_params.py | 108 +++++++++++++++- .../types/senders/agent_update_params.py | 109 +++++++++++++++- src/zavudev/types/webhook_event.py | 4 + tests/api_resources/senders/test_agent.py | 76 ++++++++++++ tests/api_resources/test_functions.py | 4 +- 8 files changed, 434 insertions(+), 11 deletions(-) diff --git a/.stats.yml b/.stats.yml index d5c85eb..55c91d6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-8d3a92fdcc7420395b9101eb0dc21d7e31581c9debb35893421d3bf81d5756f5.yml -openapi_spec_hash: 511f44a4b13dddd2bd669913849cf104 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-7b537ae3c17473b7475f562a48a26fd973e634a1e7db8877f7da9e86ad7f08b6.yml +openapi_spec_hash: 17351e9ec597d813a7a9e32555707e00 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/resources/senders/agent/agent.py b/src/zavudev/resources/senders/agent/agent.py index f3f0dc9..e7f4d8c 100644 --- a/src/zavudev/resources/senders/agent/agent.py +++ b/src/zavudev/resources/senders/agent/agent.py @@ -108,6 +108,7 @@ def create( temperature: float | Omit = omit, trigger_on_channels: SequenceNotStr[str] | Omit = omit, trigger_on_message_types: SequenceNotStr[str] | Omit = omit, + voice: agent_create_params.Voice | 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, @@ -124,6 +125,10 @@ def create( api_key: API key for the LLM provider. Required unless provider is 'zavu'. + voice: Voice Agent configuration. Enable this to let the agent answer and place phone + calls with Zavu's managed voice pipeline. Requires the Voice Agents feature to + be enabled for your team. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -149,6 +154,7 @@ def create( "temperature": temperature, "trigger_on_channels": trigger_on_channels, "trigger_on_message_types": trigger_on_message_types, + "voice": voice, }, agent_create_params.AgentCreateParams, ), @@ -207,6 +213,7 @@ def update( temperature: Optional[float] | Omit = omit, trigger_on_channels: SequenceNotStr[str] | Omit = omit, trigger_on_message_types: SequenceNotStr[str] | Omit = omit, + voice: agent_update_params.Voice | 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, @@ -220,6 +227,10 @@ def update( Args: provider: LLM provider for the AI agent. + voice: Voice Agent configuration. Patch this object to enable voice, change the + greeting, or adjust call limits. Requires the Voice Agents feature to be enabled + for your team. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -246,6 +257,7 @@ def update( "temperature": temperature, "trigger_on_channels": trigger_on_channels, "trigger_on_message_types": trigger_on_message_types, + "voice": voice, }, agent_update_params.AgentUpdateParams, ), @@ -374,6 +386,7 @@ async def create( temperature: float | Omit = omit, trigger_on_channels: SequenceNotStr[str] | Omit = omit, trigger_on_message_types: SequenceNotStr[str] | Omit = omit, + voice: agent_create_params.Voice | 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, @@ -390,6 +403,10 @@ async def create( api_key: API key for the LLM provider. Required unless provider is 'zavu'. + voice: Voice Agent configuration. Enable this to let the agent answer and place phone + calls with Zavu's managed voice pipeline. Requires the Voice Agents feature to + be enabled for your team. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -415,6 +432,7 @@ async def create( "temperature": temperature, "trigger_on_channels": trigger_on_channels, "trigger_on_message_types": trigger_on_message_types, + "voice": voice, }, agent_create_params.AgentCreateParams, ), @@ -473,6 +491,7 @@ async def update( temperature: Optional[float] | Omit = omit, trigger_on_channels: SequenceNotStr[str] | Omit = omit, trigger_on_message_types: SequenceNotStr[str] | Omit = omit, + voice: agent_update_params.Voice | 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, @@ -486,6 +505,10 @@ async def update( Args: provider: LLM provider for the AI agent. + voice: Voice Agent configuration. Patch this object to enable voice, change the + greeting, or adjust call limits. Requires the Voice Agents feature to be enabled + for your team. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -512,6 +535,7 @@ async def update( "temperature": temperature, "trigger_on_channels": trigger_on_channels, "trigger_on_message_types": trigger_on_message_types, + "voice": voice, }, agent_update_params.AgentUpdateParams, ), diff --git a/src/zavudev/types/senders/agent/agent.py b/src/zavudev/types/senders/agent/agent.py index bde9964..ff58b1b 100644 --- a/src/zavudev/types/senders/agent/agent.py +++ b/src/zavudev/types/senders/agent/agent.py @@ -1,14 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from datetime import datetime +from typing_extensions import Literal from pydantic import Field as FieldInfo from ...._models import BaseModel from ..agent_provider import AgentProvider -__all__ = ["Agent", "Stats"] +__all__ = ["Agent", "Stats", "Voice"] class Stats(BaseModel): @@ -20,6 +21,102 @@ class Stats(BaseModel): total_tokens_used: Optional[int] = FieldInfo(alias="totalTokensUsed", default=None) +class Voice(BaseModel): + """Voice Agent configuration. + + When present and enabled, the agent can answer inbound phone calls and place outbound calls with Zavu's managed voice pipeline. Requires the Voice Agents feature to be enabled for your team. + """ + + enabled: bool + """Whether the agent handles voice calls. + + When false, the sender's number is not answered by the voice agent and outbound + calls are rejected. + """ + + greeting: Optional[str] = None + """Opening line the agent speaks when the call connects. + + If omitted, the agent waits for the caller to speak first. + """ + + greetings: Optional[Dict[str, str]] = None + """Greeting per language, keyed by language code. + + Used when the caller's language differs from the one `greeting` is written in. + """ + + interruptible: Optional[bool] = None + """Whether the caller can interrupt the agent while it is speaking (barge-in). + + When true, the agent stops talking as soon as the caller starts. + """ + + language: Optional[str] = None + """BCP-47 language code used for both speech recognition and speech synthesis (e.g. + + `en`, `es`, `pt-BR`). Auto-detected from the recipient when omitted. + """ + + max_call_duration_minutes: Optional[int] = FieldInfo(alias="maxCallDurationMinutes", default=None) + """Hard limit on call length in minutes. The call ends automatically when reached.""" + + max_idle_seconds: Optional[int] = FieldInfo(alias="maxIdleSeconds", default=None) + """How long the agent waits during silence before ending the call.""" + + model: Optional[str] = None + """ + Model that runs the conversation, co-located in the voice network for lowest + latency. Independent of the model used for text messaging. Derived from the + agent's text model when omitted. + """ + + record_calls: Optional[bool] = FieldInfo(alias="recordCalls", default=None) + """Whether the call audio is recorded.""" + + stt_model: Optional[str] = FieldInfo(alias="sttModel", default=None) + """Speech-recognition model. Uses the default when omitted.""" + + stt_provider: Optional[str] = FieldInfo(alias="sttProvider", default=None) + """Speech-recognition provider. Uses the default when omitted.""" + + transfer_phone_number: Optional[str] = FieldInfo(alias="transferPhoneNumber", default=None) + """E.164 phone number the agent can transfer the call to. + + When set, the agent is given a transfer tool it can use to hand the call to a + human. + """ + + tts_provider: Optional[str] = FieldInfo(alias="ttsProvider", default=None) + """Speech-synthesis provider. Uses the default when omitted.""" + + tts_voice_id: Optional[str] = FieldInfo(alias="ttsVoiceId", default=None) + """Identifier of the synthesized voice that speaks. + + Choose from the voices available in the dashboard. Uses a neutral default when + omitted. + """ + + voicemail_action: Optional[Literal["hangup", "leave_message"]] = FieldInfo(alias="voicemailAction", default=None) + """ + What the agent does when an answering machine or voicemail is detected on an + outbound call. + """ + + voicemail_message: Optional[str] = FieldInfo(alias="voicemailMessage", default=None) + """Message spoken when `voicemailAction` is `leave_message`. + + Falls back to `greeting` when omitted. + """ + + voice_speed: Optional[float] = FieldInfo(alias="voiceSpeed", default=None) + """Speech rate. + + 1.0 is natural. Only honoured by voices that support rate control; ignored by + the others. + """ + + class Agent(BaseModel): """AI Agent configuration for a sender.""" @@ -54,6 +151,13 @@ class Agent(BaseModel): max_tokens: Optional[int] = FieldInfo(alias="maxTokens", default=None) """Maximum tokens for LLM response.""" + sender_ids: Optional[List[str]] = FieldInfo(alias="senderIds", default=None) + """Senders this agent answers on. + + An agent can serve several; `senderId` remains the primary one, for + compatibility. + """ + stats: Optional[Stats] = None temperature: Optional[float] = None @@ -64,3 +168,11 @@ class Agent(BaseModel): trigger_on_message_types: Optional[List[str]] = FieldInfo(alias="triggerOnMessageTypes", default=None) """Message types that trigger the agent.""" + + voice: Optional[Voice] = None + """Voice Agent configuration. + + When present and enabled, the agent can answer inbound phone calls and place + outbound calls with Zavu's managed voice pipeline. Requires the Voice Agents + feature to be enabled for your team. + """ diff --git a/src/zavudev/types/senders/agent_create_params.py b/src/zavudev/types/senders/agent_create_params.py index 7d612b0..4c9c460 100644 --- a/src/zavudev/types/senders/agent_create_params.py +++ b/src/zavudev/types/senders/agent_create_params.py @@ -2,13 +2,14 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict +from typing_extensions import Literal, Required, Annotated, TypedDict from ..._types import SequenceNotStr from ..._utils import PropertyInfo from .agent_provider import AgentProvider -__all__ = ["AgentCreateParams"] +__all__ = ["AgentCreateParams", "Voice"] class AgentCreateParams(TypedDict, total=False): @@ -35,3 +36,106 @@ class AgentCreateParams(TypedDict, total=False): trigger_on_channels: Annotated[SequenceNotStr[str], PropertyInfo(alias="triggerOnChannels")] trigger_on_message_types: Annotated[SequenceNotStr[str], PropertyInfo(alias="triggerOnMessageTypes")] + + voice: Voice + """Voice Agent configuration. + + Enable this to let the agent answer and place phone calls with Zavu's managed + voice pipeline. Requires the Voice Agents feature to be enabled for your team. + """ + + +class Voice(TypedDict, total=False): + """Voice Agent configuration. + + Enable this to let the agent answer and place phone calls with Zavu's managed voice pipeline. Requires the Voice Agents feature to be enabled for your team. + """ + + enabled: Required[bool] + """Whether the agent handles voice calls. + + When false, the sender's number is not answered by the voice agent and outbound + calls are rejected. + """ + + greeting: str + """Opening line the agent speaks when the call connects. + + If omitted, the agent waits for the caller to speak first. + """ + + greetings: Dict[str, str] + """Greeting per language, keyed by language code. + + Used when the caller's language differs from the one `greeting` is written in. + """ + + interruptible: bool + """Whether the caller can interrupt the agent while it is speaking (barge-in). + + When true, the agent stops talking as soon as the caller starts. + """ + + language: str + """BCP-47 language code used for both speech recognition and speech synthesis (e.g. + + `en`, `es`, `pt-BR`). Auto-detected from the recipient when omitted. + """ + + max_call_duration_minutes: Annotated[int, PropertyInfo(alias="maxCallDurationMinutes")] + """Hard limit on call length in minutes. The call ends automatically when reached.""" + + max_idle_seconds: Annotated[int, PropertyInfo(alias="maxIdleSeconds")] + """How long the agent waits during silence before ending the call.""" + + model: str + """ + Model that runs the conversation, co-located in the voice network for lowest + latency. Independent of the model used for text messaging. Derived from the + agent's text model when omitted. + """ + + record_calls: Annotated[bool, PropertyInfo(alias="recordCalls")] + """Whether the call audio is recorded.""" + + stt_model: Annotated[str, PropertyInfo(alias="sttModel")] + """Speech-recognition model. Uses the default when omitted.""" + + stt_provider: Annotated[str, PropertyInfo(alias="sttProvider")] + """Speech-recognition provider. Uses the default when omitted.""" + + transfer_phone_number: Annotated[str, PropertyInfo(alias="transferPhoneNumber")] + """E.164 phone number the agent can transfer the call to. + + When set, the agent is given a transfer tool it can use to hand the call to a + human. + """ + + tts_provider: Annotated[str, PropertyInfo(alias="ttsProvider")] + """Speech-synthesis provider. Uses the default when omitted.""" + + tts_voice_id: Annotated[str, PropertyInfo(alias="ttsVoiceId")] + """Identifier of the synthesized voice that speaks. + + Choose from the voices available in the dashboard. Uses a neutral default when + omitted. + """ + + voicemail_action: Annotated[Literal["hangup", "leave_message"], PropertyInfo(alias="voicemailAction")] + """ + What the agent does when an answering machine or voicemail is detected on an + outbound call. + """ + + voicemail_message: Annotated[str, PropertyInfo(alias="voicemailMessage")] + """Message spoken when `voicemailAction` is `leave_message`. + + Falls back to `greeting` when omitted. + """ + + voice_speed: Annotated[float, PropertyInfo(alias="voiceSpeed")] + """Speech rate. + + 1.0 is natural. Only honoured by voices that support rate control; ignored by + the others. + """ diff --git a/src/zavudev/types/senders/agent_update_params.py b/src/zavudev/types/senders/agent_update_params.py index 58d7341..47dcd81 100644 --- a/src/zavudev/types/senders/agent_update_params.py +++ b/src/zavudev/types/senders/agent_update_params.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Annotated, TypedDict +from typing import Dict, Optional +from typing_extensions import Literal, Required, Annotated, TypedDict from ..._types import SequenceNotStr from ..._utils import PropertyInfo from .agent_provider import AgentProvider -__all__ = ["AgentUpdateParams"] +__all__ = ["AgentUpdateParams", "Voice"] class AgentUpdateParams(TypedDict, total=False): @@ -37,3 +37,106 @@ class AgentUpdateParams(TypedDict, total=False): trigger_on_channels: Annotated[SequenceNotStr[str], PropertyInfo(alias="triggerOnChannels")] trigger_on_message_types: Annotated[SequenceNotStr[str], PropertyInfo(alias="triggerOnMessageTypes")] + + voice: Voice + """Voice Agent configuration. + + Patch this object to enable voice, change the greeting, or adjust call limits. + Requires the Voice Agents feature to be enabled for your team. + """ + + +class Voice(TypedDict, total=False): + """Voice Agent configuration. + + Patch this object to enable voice, change the greeting, or adjust call limits. Requires the Voice Agents feature to be enabled for your team. + """ + + enabled: Required[bool] + """Whether the agent handles voice calls. + + When false, the sender's number is not answered by the voice agent and outbound + calls are rejected. + """ + + greeting: str + """Opening line the agent speaks when the call connects. + + If omitted, the agent waits for the caller to speak first. + """ + + greetings: Dict[str, str] + """Greeting per language, keyed by language code. + + Used when the caller's language differs from the one `greeting` is written in. + """ + + interruptible: bool + """Whether the caller can interrupt the agent while it is speaking (barge-in). + + When true, the agent stops talking as soon as the caller starts. + """ + + language: str + """BCP-47 language code used for both speech recognition and speech synthesis (e.g. + + `en`, `es`, `pt-BR`). Auto-detected from the recipient when omitted. + """ + + max_call_duration_minutes: Annotated[int, PropertyInfo(alias="maxCallDurationMinutes")] + """Hard limit on call length in minutes. The call ends automatically when reached.""" + + max_idle_seconds: Annotated[int, PropertyInfo(alias="maxIdleSeconds")] + """How long the agent waits during silence before ending the call.""" + + model: str + """ + Model that runs the conversation, co-located in the voice network for lowest + latency. Independent of the model used for text messaging. Derived from the + agent's text model when omitted. + """ + + record_calls: Annotated[bool, PropertyInfo(alias="recordCalls")] + """Whether the call audio is recorded.""" + + stt_model: Annotated[str, PropertyInfo(alias="sttModel")] + """Speech-recognition model. Uses the default when omitted.""" + + stt_provider: Annotated[str, PropertyInfo(alias="sttProvider")] + """Speech-recognition provider. Uses the default when omitted.""" + + transfer_phone_number: Annotated[str, PropertyInfo(alias="transferPhoneNumber")] + """E.164 phone number the agent can transfer the call to. + + When set, the agent is given a transfer tool it can use to hand the call to a + human. + """ + + tts_provider: Annotated[str, PropertyInfo(alias="ttsProvider")] + """Speech-synthesis provider. Uses the default when omitted.""" + + tts_voice_id: Annotated[str, PropertyInfo(alias="ttsVoiceId")] + """Identifier of the synthesized voice that speaks. + + Choose from the voices available in the dashboard. Uses a neutral default when + omitted. + """ + + voicemail_action: Annotated[Literal["hangup", "leave_message"], PropertyInfo(alias="voicemailAction")] + """ + What the agent does when an answering machine or voicemail is detected on an + outbound call. + """ + + voicemail_message: Annotated[str, PropertyInfo(alias="voicemailMessage")] + """Message spoken when `voicemailAction` is `leave_message`. + + Falls back to `greeting` when omitted. + """ + + voice_speed: Annotated[float, PropertyInfo(alias="voiceSpeed")] + """Speech rate. + + 1.0 is natural. Only honoured by voices that support rate control; ignored by + the others. + """ diff --git a/src/zavudev/types/webhook_event.py b/src/zavudev/types/webhook_event.py index 8f34dee..eb64bcc 100644 --- a/src/zavudev/types/webhook_event.py +++ b/src/zavudev/types/webhook_event.py @@ -16,6 +16,10 @@ "conversation.new", "template.status_changed", "invitation.status_changed", + "call.initiated", + "call.answered", + "call.completed", + "call.failed", "domain.verified", "domain.failed", ] diff --git a/tests/api_resources/senders/test_agent.py b/tests/api_resources/senders/test_agent.py index b5f27fa..af4fb8f 100644 --- a/tests/api_resources/senders/test_agent.py +++ b/tests/api_resources/senders/test_agent.py @@ -45,6 +45,25 @@ def test_method_create_with_all_params(self, client: Zavudev) -> None: temperature=0, trigger_on_channels=["string"], trigger_on_message_types=["string"], + voice={ + "enabled": True, + "greeting": "Hi, thanks for calling Acme. How can I help you today?", + "greetings": {"es": "Hola, soy Atlas. Preguntame lo que quieras."}, + "interruptible": True, + "language": "en", + "max_call_duration_minutes": 1, + "max_idle_seconds": 5, + "model": "openai/gpt-4o", + "record_calls": True, + "stt_model": "sttModel", + "stt_provider": "sttProvider", + "transfer_phone_number": "+14155551234", + "tts_provider": "ttsProvider", + "tts_voice_id": "aria", + "voicemail_action": "hangup", + "voicemail_message": "voicemailMessage", + "voice_speed": 0.5, + }, ) assert_matches_type(AgentResponse, agent, path=["response"]) @@ -161,6 +180,25 @@ def test_method_update_with_all_params(self, client: Zavudev) -> None: temperature=0, trigger_on_channels=["string"], trigger_on_message_types=["string"], + voice={ + "enabled": True, + "greeting": "Hi, thanks for calling Acme. How can I help you today?", + "greetings": {"es": "Hola, soy Atlas. Preguntame lo que quieras."}, + "interruptible": True, + "language": "en", + "max_call_duration_minutes": 1, + "max_idle_seconds": 5, + "model": "openai/gpt-4o", + "record_calls": True, + "stt_model": "sttModel", + "stt_provider": "sttProvider", + "transfer_phone_number": "+14155551234", + "tts_provider": "ttsProvider", + "tts_voice_id": "aria", + "voicemail_action": "hangup", + "voicemail_message": "voicemailMessage", + "voice_speed": 0.5, + }, ) assert_matches_type(AgentResponse, agent, path=["response"]) @@ -316,6 +354,25 @@ async def test_method_create_with_all_params(self, async_client: AsyncZavudev) - temperature=0, trigger_on_channels=["string"], trigger_on_message_types=["string"], + voice={ + "enabled": True, + "greeting": "Hi, thanks for calling Acme. How can I help you today?", + "greetings": {"es": "Hola, soy Atlas. Preguntame lo que quieras."}, + "interruptible": True, + "language": "en", + "max_call_duration_minutes": 1, + "max_idle_seconds": 5, + "model": "openai/gpt-4o", + "record_calls": True, + "stt_model": "sttModel", + "stt_provider": "sttProvider", + "transfer_phone_number": "+14155551234", + "tts_provider": "ttsProvider", + "tts_voice_id": "aria", + "voicemail_action": "hangup", + "voicemail_message": "voicemailMessage", + "voice_speed": 0.5, + }, ) assert_matches_type(AgentResponse, agent, path=["response"]) @@ -432,6 +489,25 @@ async def test_method_update_with_all_params(self, async_client: AsyncZavudev) - temperature=0, trigger_on_channels=["string"], trigger_on_message_types=["string"], + voice={ + "enabled": True, + "greeting": "Hi, thanks for calling Acme. How can I help you today?", + "greetings": {"es": "Hola, soy Atlas. Preguntame lo que quieras."}, + "interruptible": True, + "language": "en", + "max_call_duration_minutes": 1, + "max_idle_seconds": 5, + "model": "openai/gpt-4o", + "record_calls": True, + "stt_model": "sttModel", + "stt_provider": "sttProvider", + "transfer_phone_number": "+14155551234", + "tts_provider": "ttsProvider", + "tts_voice_id": "aria", + "voicemail_action": "hangup", + "voicemail_message": "voicemailMessage", + "voice_speed": 0.5, + }, ) assert_matches_type(AgentResponse, agent, path=["response"]) diff --git a/tests/api_resources/test_functions.py b/tests/api_resources/test_functions.py index 72d4b43..c002192 100644 --- a/tests/api_resources/test_functions.py +++ b/tests/api_resources/test_functions.py @@ -45,7 +45,7 @@ def test_method_create_with_all_params(self, client: Zavudev) -> None: http_enabled=True, memory_mb=128, runtime="nodejs24", - source_code="import { defineFunction } from '@zavu/functions';\n\nexport default defineFunction(async (event, ctx) => {\n ctx.log('received', event.type);\n});\n", + source_code="import { defineFunction } from '@zavudev/functions';\n\nexport default defineFunction(async (event, ctx) => {\n ctx.log('received', event.type);\n});\n", timeout_sec=1, ) assert_matches_type(FunctionCreateResponse, function, path=["response"]) @@ -389,7 +389,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncZavudev) - http_enabled=True, memory_mb=128, runtime="nodejs24", - source_code="import { defineFunction } from '@zavu/functions';\n\nexport default defineFunction(async (event, ctx) => {\n ctx.log('received', event.type);\n});\n", + source_code="import { defineFunction } from '@zavudev/functions';\n\nexport default defineFunction(async (event, ctx) => {\n ctx.log('received', event.type);\n});\n", timeout_sec=1, ) assert_matches_type(FunctionCreateResponse, function, path=["response"]) From 651eca7024525cb98251498dab6939f3b9d363a6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:35:36 +0000 Subject: [PATCH 02/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/types/webhook_event.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 55c91d6..ede9ce1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-7b537ae3c17473b7475f562a48a26fd973e634a1e7db8877f7da9e86ad7f08b6.yml -openapi_spec_hash: 17351e9ec597d813a7a9e32555707e00 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-7d14c089322b2a138284a27ccccfa73f992f6384670a1829bb96fb21abb55d45.yml +openapi_spec_hash: 2c3e25ab64e3b1c751f85ca7cdeb2993 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/types/webhook_event.py b/src/zavudev/types/webhook_event.py index eb64bcc..c238498 100644 --- a/src/zavudev/types/webhook_event.py +++ b/src/zavudev/types/webhook_event.py @@ -9,6 +9,7 @@ "message.sent", "message.delivered", "message.read", + "message.status", "message.failed", "message.inbound", "message.unsupported", From fe4c991998a0838466edce4e46e61b1fb212a470 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:41:32 +0000 Subject: [PATCH 03/13] feat(api): api update --- .stats.yml | 4 +-- src/zavudev/resources/functions/functions.py | 38 ++++++++++++++------ src/zavudev/types/function_update_params.py | 9 ++++- tests/api_resources/test_functions.py | 2 ++ 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/.stats.yml b/.stats.yml index ede9ce1..38d111e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-7d14c089322b2a138284a27ccccfa73f992f6384670a1829bb96fb21abb55d45.yml -openapi_spec_hash: 2c3e25ab64e3b1c751f85ca7cdeb2993 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-246afdec993c765eca25d57d9bdafb21187c7caff599ca7201cfe4722133bb3b.yml +openapi_spec_hash: da6182b3a5be80b5fa62828eecde771f config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/resources/functions/functions.py b/src/zavudev/resources/functions/functions.py index e550697..ab96ec4 100644 --- a/src/zavudev/resources/functions/functions.py +++ b/src/zavudev/resources/functions/functions.py @@ -169,6 +169,7 @@ def update( function_id: str, *, dependencies: Dict[str, str] | Omit = omit, + http_enabled: bool | Omit = omit, source_code: 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. @@ -177,15 +178,22 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FunctionUpdateResponse: - """ - Update the draft source code and/or dependency map without triggering a build. - Visible in the dashboard immediately, but the live (deployed) function does not - change until `POST /v1/functions/{functionId}/deploy` runs. + """Update an existing function. + + `sourceCode` / `dependencies` edit the draft + without triggering a build — they go live on the next + `POST /v1/functions/{functionId}/deploy`. `httpEnabled` is applied to the + deployed function immediately, so turning the public endpoint on or off does not + require a redeploy. Args: dependencies: New dependency map (replaces existing dependencies). - source_code: New source code to publish (replaces the draft). + http_enabled: Expose the function on its public HTTPS URL, or take it down. Applies to the + already-deployed function without redeploying; the URL is returned as + `publicUrl`. + + source_code: New source code for the draft (replaces it). extra_headers: Send extra headers @@ -202,6 +210,7 @@ def update( body=maybe_transform( { "dependencies": dependencies, + "http_enabled": http_enabled, "source_code": source_code, }, function_update_params.FunctionUpdateParams, @@ -520,6 +529,7 @@ async def update( function_id: str, *, dependencies: Dict[str, str] | Omit = omit, + http_enabled: bool | Omit = omit, source_code: 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. @@ -528,15 +538,22 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FunctionUpdateResponse: - """ - Update the draft source code and/or dependency map without triggering a build. - Visible in the dashboard immediately, but the live (deployed) function does not - change until `POST /v1/functions/{functionId}/deploy` runs. + """Update an existing function. + + `sourceCode` / `dependencies` edit the draft + without triggering a build — they go live on the next + `POST /v1/functions/{functionId}/deploy`. `httpEnabled` is applied to the + deployed function immediately, so turning the public endpoint on or off does not + require a redeploy. Args: dependencies: New dependency map (replaces existing dependencies). - source_code: New source code to publish (replaces the draft). + http_enabled: Expose the function on its public HTTPS URL, or take it down. Applies to the + already-deployed function without redeploying; the URL is returned as + `publicUrl`. + + source_code: New source code for the draft (replaces it). extra_headers: Send extra headers @@ -553,6 +570,7 @@ async def update( body=await async_maybe_transform( { "dependencies": dependencies, + "http_enabled": http_enabled, "source_code": source_code, }, function_update_params.FunctionUpdateParams, diff --git a/src/zavudev/types/function_update_params.py b/src/zavudev/types/function_update_params.py index 354cc5e..65a687b 100644 --- a/src/zavudev/types/function_update_params.py +++ b/src/zavudev/types/function_update_params.py @@ -14,5 +14,12 @@ class FunctionUpdateParams(TypedDict, total=False): dependencies: Dict[str, str] """New dependency map (replaces existing dependencies).""" + http_enabled: Annotated[bool, PropertyInfo(alias="httpEnabled")] + """Expose the function on its public HTTPS URL, or take it down. + + Applies to the already-deployed function without redeploying; the URL is + returned as `publicUrl`. + """ + source_code: Annotated[str, PropertyInfo(alias="sourceCode")] - """New source code to publish (replaces the draft).""" + """New source code for the draft (replaces it).""" diff --git a/tests/api_resources/test_functions.py b/tests/api_resources/test_functions.py index c002192..140fae2 100644 --- a/tests/api_resources/test_functions.py +++ b/tests/api_resources/test_functions.py @@ -134,6 +134,7 @@ def test_method_update_with_all_params(self, client: Zavudev) -> None: function = client.functions.update( function_id="functionId", dependencies={"foo": "string"}, + http_enabled=True, source_code="sourceCode", ) assert_matches_type(FunctionUpdateResponse, function, path=["response"]) @@ -478,6 +479,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncZavudev) - function = await async_client.functions.update( function_id="functionId", dependencies={"foo": "string"}, + http_enabled=True, source_code="sourceCode", ) assert_matches_type(FunctionUpdateResponse, function, path=["response"]) From 5a5e32773275af52862c9e4d00e98f00052ecc5f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:49:24 +0000 Subject: [PATCH 04/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/types/function_create_response.py | 6 +++++- src/zavudev/types/function_retrieve_response.py | 6 +++++- src/zavudev/types/function_update_response.py | 6 +++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 38d111e..71c5e9c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-246afdec993c765eca25d57d9bdafb21187c7caff599ca7201cfe4722133bb3b.yml -openapi_spec_hash: da6182b3a5be80b5fa62828eecde771f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-e4456da6947e80317d37fab71a89adc8da46da776384907d1a5bec745a493845.yml +openapi_spec_hash: c544af51a36ae73ac3fb6dae95301ee9 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/types/function_create_response.py b/src/zavudev/types/function_create_response.py index 3603b06..53caecc 100644 --- a/src/zavudev/types/function_create_response.py +++ b/src/zavudev/types/function_create_response.py @@ -54,7 +54,11 @@ class Function(BaseModel): description: Optional[str] = None public_url: Optional[str] = FieldInfo(alias="publicUrl", default=None) - """HTTPS endpoint when httpEnabled is true.""" + """HTTPS endpoint, present only while httpEnabled is true. + + Null otherwise, including for a function that was previously exposed — the + stored URL stops serving the moment HTTP is turned off, so it is never returned. + """ class FunctionCreateResponse(BaseModel): diff --git a/src/zavudev/types/function_retrieve_response.py b/src/zavudev/types/function_retrieve_response.py index c67c3b3..752b979 100644 --- a/src/zavudev/types/function_retrieve_response.py +++ b/src/zavudev/types/function_retrieve_response.py @@ -54,7 +54,11 @@ class Function(BaseModel): description: Optional[str] = None public_url: Optional[str] = FieldInfo(alias="publicUrl", default=None) - """HTTPS endpoint when httpEnabled is true.""" + """HTTPS endpoint, present only while httpEnabled is true. + + Null otherwise, including for a function that was previously exposed — the + stored URL stops serving the moment HTTP is turned off, so it is never returned. + """ class FunctionRetrieveResponse(BaseModel): diff --git a/src/zavudev/types/function_update_response.py b/src/zavudev/types/function_update_response.py index 4c3c145..7ee11bb 100644 --- a/src/zavudev/types/function_update_response.py +++ b/src/zavudev/types/function_update_response.py @@ -54,7 +54,11 @@ class Function(BaseModel): description: Optional[str] = None public_url: Optional[str] = FieldInfo(alias="publicUrl", default=None) - """HTTPS endpoint when httpEnabled is true.""" + """HTTPS endpoint, present only while httpEnabled is true. + + Null otherwise, including for a function that was previously exposed — the + stored URL stops serving the moment HTTP is turned off, so it is never returned. + """ class FunctionUpdateResponse(BaseModel): From 5954d051a7563b2b59971a37cb465813c929b088 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:50:01 +0000 Subject: [PATCH 05/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/types/message.py | 6 ++++++ src/zavudev/types/message_type.py | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 71c5e9c..f0a8027 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-e4456da6947e80317d37fab71a89adc8da46da776384907d1a5bec745a493845.yml -openapi_spec_hash: c544af51a36ae73ac3fb6dae95301ee9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-55f1df28791998d2f611399571f97641cd28af2f954d505fa7ebe048dd5c3906.yml +openapi_spec_hash: aff851ef1467cf22deaaa517f2137409 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/types/message.py b/src/zavudev/types/message.py index 51351a2..19047d0 100644 --- a/src/zavudev/types/message.py +++ b/src/zavudev/types/message.py @@ -26,6 +26,12 @@ class Message(BaseModel): """Type of message. Non-text types are supported by WhatsApp and Telegram (varies by type). + + `location_request` asks the recipient to share their location and is + WhatsApp-only. It takes no `content` object — the prompt goes in `text` (max + 1024 characters) and the button label is fixed by WhatsApp. The recipient's + answer arrives as an inbound `location` message whose `content.replyToMessageId` + is the ID of the request. """ status: MessageStatus diff --git a/src/zavudev/types/message_type.py b/src/zavudev/types/message_type.py index bab7e27..7a886a7 100644 --- a/src/zavudev/types/message_type.py +++ b/src/zavudev/types/message_type.py @@ -16,6 +16,7 @@ "buttons", "list", "cta_url", + "location_request", "reaction", "template", ] From eab4046ff43c57b684917c7fc107f9c1ca8d582e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:34:43 +0000 Subject: [PATCH 06/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/types/sender.py | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index f0a8027..713d8dc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-55f1df28791998d2f611399571f97641cd28af2f954d505fa7ebe048dd5c3906.yml -openapi_spec_hash: aff851ef1467cf22deaaa517f2137409 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-de0f0a0b7a37dab94c8398c40404b342418694a41e7d3555668d0cc870b5806d.yml +openapi_spec_hash: 7f29dac9d528e707be2f3e6fd28f4f81 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/types/sender.py b/src/zavudev/types/sender.py index e63c68a..4991ea6 100644 --- a/src/zavudev/types/sender.py +++ b/src/zavudev/types/sender.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime from pydantic import Field as FieldInfo @@ -48,6 +48,14 @@ class Sender(BaseModel): phone_number: str = FieldInfo(alias="phoneNumber") """Phone number in E.164 format.""" + channels: Optional[List[str]] = None + """ + Channels this sender can actually send on right now, computed from its + configuration. Empty means the sender cannot send or receive anything yet: a + phoneNumber alone does not enable SMS or voice. Check this rather than inferring + capability from phoneNumber or emailAddress. + """ + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) email_address: Optional[str] = FieldInfo(alias="emailAddress", default=None) From acf9e008feae130193c9b12759c3ac6801c0b58e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:26:28 +0000 Subject: [PATCH 07/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/resources/senders/agent/agent.py | 12 ++++++++++++ src/zavudev/resources/senders/agent/executions.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 713d8dc..5154c91 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-de0f0a0b7a37dab94c8398c40404b342418694a41e7d3555668d0cc870b5806d.yml -openapi_spec_hash: 7f29dac9d528e707be2f3e6fd28f4f81 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-8a74f855486f312075e70dcf295fb0c024e19b5a4595c53833aaf401e825ca92.yml +openapi_spec_hash: 7ca5dfcfe008df918b3d824c5349c4de config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/resources/senders/agent/agent.py b/src/zavudev/resources/senders/agent/agent.py index e7f4d8c..39ea5bc 100644 --- a/src/zavudev/resources/senders/agent/agent.py +++ b/src/zavudev/resources/senders/agent/agent.py @@ -315,6 +315,12 @@ def stats( """ Get statistics for an AI agent including invocations, tokens, and costs. + Covers the messaging channels only. Voice calls are not counted here: a call is + a multi-turn conversation rather than one inbound message and one reply, so it + is recorded as a call, not an execution. An agent that only answers phone calls + reports zeros on every field. Use `GET /v1/calls` for voice activity, duration, + and cost. + Args: extra_headers: Send extra headers @@ -593,6 +599,12 @@ async def stats( """ Get statistics for an AI agent including invocations, tokens, and costs. + Covers the messaging channels only. Voice calls are not counted here: a call is + a multi-turn conversation rather than one inbound message and one reply, so it + is recorded as a call, not an execution. An agent that only answers phone calls + reports zeros on every field. Use `GET /v1/calls` for voice activity, duration, + and cost. + Args: extra_headers: Send extra headers diff --git a/src/zavudev/resources/senders/agent/executions.py b/src/zavudev/resources/senders/agent/executions.py index f391e44..22a54b9 100644 --- a/src/zavudev/resources/senders/agent/executions.py +++ b/src/zavudev/resources/senders/agent/executions.py @@ -103,6 +103,11 @@ def list( """ List recent agent executions with pagination. + An execution is one inbound message answered by the agent, so this covers the + messaging channels only. Voice calls are never listed here regardless of how + many the agent handled. Use `GET /v1/calls` (and `GET /v1/calls/{callId}` for + the transcript) for voice. + Args: status: Status of an agent execution. @@ -215,6 +220,11 @@ def list( """ List recent agent executions with pagination. + An execution is one inbound message answered by the agent, so this covers the + messaging channels only. Voice calls are never listed here regardless of how + many the agent handled. Use `GET /v1/calls` (and `GET /v1/calls/{callId}` for + the transcript) for voice. + Args: status: Status of an agent execution. From 1cafed2e21dfccb90a4ab09b98525be28d7481c9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:53:59 +0000 Subject: [PATCH 08/13] feat(api): api update --- .stats.yml | 4 +- src/zavudev/resources/phone_numbers.py | 10 +++++ src/zavudev/resources/senders/senders.py | 38 +++++++++++++++++-- .../phone_number_search_available_params.py | 6 +++ src/zavudev/types/sender_create_params.py | 17 +++++++-- src/zavudev/types/sender_update_params.py | 8 ++++ tests/api_resources/test_phone_numbers.py | 2 + tests/api_resources/test_senders.py | 4 ++ 8 files changed, 79 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5154c91..ab4c608 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-8a74f855486f312075e70dcf295fb0c024e19b5a4595c53833aaf401e825ca92.yml -openapi_spec_hash: 7ca5dfcfe008df918b3d824c5349c4de +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-6c9214bc9b9e686d156cb81aeb3a68901168476cf5c1b1ceb4e70e92753d2eab.yml +openapi_spec_hash: be99aad1d6513b6037a3c2b07db3350c config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/resources/phone_numbers.py b/src/zavudev/resources/phone_numbers.py index ad43f9b..6aace0e 100644 --- a/src/zavudev/resources/phone_numbers.py +++ b/src/zavudev/resources/phone_numbers.py @@ -319,6 +319,7 @@ def search_available( self, *, country_code: str, + capabilities: str | Omit = omit, contains: str | Omit = omit, limit: int | Omit = omit, type: PhoneNumberType | Omit = omit, @@ -335,6 +336,9 @@ def search_available( Args: country_code: Two-letter ISO country code. + capabilities: Comma-separated capabilities the number must have: `sms`, `voice`, `mms`. + Numbers missing any of them are dropped. + contains: Search for numbers containing this string. limit: Maximum number of results to return. @@ -359,6 +363,7 @@ def search_available( query=maybe_transform( { "country_code": country_code, + "capabilities": capabilities, "contains": contains, "limit": limit, "type": type, @@ -650,6 +655,7 @@ async def search_available( self, *, country_code: str, + capabilities: str | Omit = omit, contains: str | Omit = omit, limit: int | Omit = omit, type: PhoneNumberType | Omit = omit, @@ -666,6 +672,9 @@ async def search_available( Args: country_code: Two-letter ISO country code. + capabilities: Comma-separated capabilities the number must have: `sms`, `voice`, `mms`. + Numbers missing any of them are dropped. + contains: Search for numbers containing this string. limit: Maximum number of results to return. @@ -690,6 +699,7 @@ async def search_available( query=await async_maybe_transform( { "country_code": country_code, + "capabilities": capabilities, "contains": contains, "limit": limit, "type": type, diff --git a/src/zavudev/resources/senders/senders.py b/src/zavudev/resources/senders/senders.py index 062919b..738a918 100644 --- a/src/zavudev/resources/senders/senders.py +++ b/src/zavudev/resources/senders/senders.py @@ -90,6 +90,7 @@ def create( email_domain_id: str | Omit = omit, email_from_name: str | Omit = omit, email_receiving_enabled: bool | Omit = omit, + enable_voice: bool | Omit = omit, phone_number: str | Omit = omit, set_as_default: bool | Omit = omit, webhook_events: List[WebhookEvent] | Omit = omit, @@ -118,8 +119,15 @@ def create( email_receiving_enabled: Enable inbound email receiving on this sender. Requires a verified MX record on the domain; ignored otherwise. - phone_number: Phone number in E.164 format. Required for phone-based channels (SMS, WhatsApp). - Omit for an email-only sender. + enable_voice: Let this sender place and answer phone calls. Requires `phoneNumber`; enabling + it without one returns 400. Check the `channels` array on the response to + confirm `voice` is on. + + phone_number: Phone number in E.164 format, and it must be a number your project already owns + (see `GET /v1/phone-numbers`). The number is routed to the sender as part of + this call, which is what turns the SMS channel on. Passing a number the project + does not own, or one already attached to another sender, returns 400 rather than + creating a sender that cannot send. Omit for an email-only sender. webhook_events: Events to subscribe to. @@ -142,6 +150,7 @@ def create( "email_domain_id": email_domain_id, "email_from_name": email_from_name, "email_receiving_enabled": email_receiving_enabled, + "enable_voice": enable_voice, "phone_number": phone_number, "set_as_default": set_as_default, "webhook_events": webhook_events, @@ -197,6 +206,7 @@ def update( email_domain_id: str | Omit = omit, email_from_name: str | Omit = omit, email_receiving_enabled: bool | Omit = omit, + enable_voice: bool | Omit = omit, name: str | Omit = omit, set_as_default: bool | Omit = omit, webhook_active: bool | Omit = omit, @@ -227,6 +237,10 @@ def update( email_receiving_enabled: Enable or disable inbound email receiving for this sender. + enable_voice: Turn the voice channel on or off. The sender must already have a phone number + provisioned for calls; enabling it otherwise returns 400 instead of storing a + flag that changes nothing. Confirm with the `channels` array on the response. + webhook_active: Whether the webhook is active. webhook_events: Events to subscribe to. @@ -252,6 +266,7 @@ def update( "email_domain_id": email_domain_id, "email_from_name": email_from_name, "email_receiving_enabled": email_receiving_enabled, + "enable_voice": enable_voice, "name": name, "set_as_default": set_as_default, "webhook_active": webhook_active, @@ -562,6 +577,7 @@ async def create( email_domain_id: str | Omit = omit, email_from_name: str | Omit = omit, email_receiving_enabled: bool | Omit = omit, + enable_voice: bool | Omit = omit, phone_number: str | Omit = omit, set_as_default: bool | Omit = omit, webhook_events: List[WebhookEvent] | Omit = omit, @@ -590,8 +606,15 @@ async def create( email_receiving_enabled: Enable inbound email receiving on this sender. Requires a verified MX record on the domain; ignored otherwise. - phone_number: Phone number in E.164 format. Required for phone-based channels (SMS, WhatsApp). - Omit for an email-only sender. + enable_voice: Let this sender place and answer phone calls. Requires `phoneNumber`; enabling + it without one returns 400. Check the `channels` array on the response to + confirm `voice` is on. + + phone_number: Phone number in E.164 format, and it must be a number your project already owns + (see `GET /v1/phone-numbers`). The number is routed to the sender as part of + this call, which is what turns the SMS channel on. Passing a number the project + does not own, or one already attached to another sender, returns 400 rather than + creating a sender that cannot send. Omit for an email-only sender. webhook_events: Events to subscribe to. @@ -614,6 +637,7 @@ async def create( "email_domain_id": email_domain_id, "email_from_name": email_from_name, "email_receiving_enabled": email_receiving_enabled, + "enable_voice": enable_voice, "phone_number": phone_number, "set_as_default": set_as_default, "webhook_events": webhook_events, @@ -669,6 +693,7 @@ async def update( email_domain_id: str | Omit = omit, email_from_name: str | Omit = omit, email_receiving_enabled: bool | Omit = omit, + enable_voice: bool | Omit = omit, name: str | Omit = omit, set_as_default: bool | Omit = omit, webhook_active: bool | Omit = omit, @@ -699,6 +724,10 @@ async def update( email_receiving_enabled: Enable or disable inbound email receiving for this sender. + enable_voice: Turn the voice channel on or off. The sender must already have a phone number + provisioned for calls; enabling it otherwise returns 400 instead of storing a + flag that changes nothing. Confirm with the `channels` array on the response. + webhook_active: Whether the webhook is active. webhook_events: Events to subscribe to. @@ -724,6 +753,7 @@ async def update( "email_domain_id": email_domain_id, "email_from_name": email_from_name, "email_receiving_enabled": email_receiving_enabled, + "enable_voice": enable_voice, "name": name, "set_as_default": set_as_default, "webhook_active": webhook_active, diff --git a/src/zavudev/types/phone_number_search_available_params.py b/src/zavudev/types/phone_number_search_available_params.py index 24d5470..516f9c3 100644 --- a/src/zavudev/types/phone_number_search_available_params.py +++ b/src/zavudev/types/phone_number_search_available_params.py @@ -14,6 +14,12 @@ class PhoneNumberSearchAvailableParams(TypedDict, total=False): country_code: Required[Annotated[str, PropertyInfo(alias="countryCode")]] """Two-letter ISO country code.""" + capabilities: str + """Comma-separated capabilities the number must have: `sms`, `voice`, `mms`. + + Numbers missing any of them are dropped. + """ + contains: str """Search for numbers containing this string.""" diff --git a/src/zavudev/types/sender_create_params.py b/src/zavudev/types/sender_create_params.py index e49710d..2910d8f 100644 --- a/src/zavudev/types/sender_create_params.py +++ b/src/zavudev/types/sender_create_params.py @@ -36,11 +36,20 @@ class SenderCreateParams(TypedDict, total=False): Requires a verified MX record on the domain; ignored otherwise. """ - phone_number: Annotated[str, PropertyInfo(alias="phoneNumber")] - """Phone number in E.164 format. + enable_voice: Annotated[bool, PropertyInfo(alias="enableVoice")] + """Let this sender place and answer phone calls. + + Requires `phoneNumber`; enabling it without one returns 400. Check the + `channels` array on the response to confirm `voice` is on. + """ - Required for phone-based channels (SMS, WhatsApp). Omit for an email-only - sender. + phone_number: Annotated[str, PropertyInfo(alias="phoneNumber")] + """ + Phone number in E.164 format, and it must be a number your project already owns + (see `GET /v1/phone-numbers`). The number is routed to the sender as part of + this call, which is what turns the SMS channel on. Passing a number the project + does not own, or one already attached to another sender, returns 400 rather than + creating a sender that cannot send. Omit for an email-only sender. """ set_as_default: Annotated[bool, PropertyInfo(alias="setAsDefault")] diff --git a/src/zavudev/types/sender_update_params.py b/src/zavudev/types/sender_update_params.py index fa25334..3c58022 100644 --- a/src/zavudev/types/sender_update_params.py +++ b/src/zavudev/types/sender_update_params.py @@ -39,6 +39,14 @@ class SenderUpdateParams(TypedDict, total=False): email_receiving_enabled: Annotated[bool, PropertyInfo(alias="emailReceivingEnabled")] """Enable or disable inbound email receiving for this sender.""" + enable_voice: Annotated[bool, PropertyInfo(alias="enableVoice")] + """Turn the voice channel on or off. + + The sender must already have a phone number provisioned for calls; enabling it + otherwise returns 400 instead of storing a flag that changes nothing. Confirm + with the `channels` array on the response. + """ + name: str set_as_default: Annotated[bool, PropertyInfo(alias="setAsDefault")] diff --git a/tests/api_resources/test_phone_numbers.py b/tests/api_resources/test_phone_numbers.py index 0492bb0..3e8f6ab 100644 --- a/tests/api_resources/test_phone_numbers.py +++ b/tests/api_resources/test_phone_numbers.py @@ -298,6 +298,7 @@ def test_method_search_available(self, client: Zavudev) -> None: def test_method_search_available_with_all_params(self, client: Zavudev) -> None: phone_number = client.phone_numbers.search_available( country_code="xx", + capabilities="voice,sms", contains="contains", limit=50, type="local", @@ -609,6 +610,7 @@ async def test_method_search_available(self, async_client: AsyncZavudev) -> None async def test_method_search_available_with_all_params(self, async_client: AsyncZavudev) -> None: phone_number = await async_client.phone_numbers.search_available( country_code="xx", + capabilities="voice,sms", contains="contains", limit=50, type="local", diff --git a/tests/api_resources/test_senders.py b/tests/api_resources/test_senders.py index 698aa49..426466c 100644 --- a/tests/api_resources/test_senders.py +++ b/tests/api_resources/test_senders.py @@ -41,6 +41,7 @@ def test_method_create_with_all_params(self, client: Zavudev) -> None: email_domain_id="emailDomainId", email_from_name="emailFromName", email_receiving_enabled=True, + enable_voice=True, phone_number="phoneNumber", set_as_default=True, webhook_events=["message.queued"], @@ -134,6 +135,7 @@ def test_method_update_with_all_params(self, client: Zavudev) -> None: email_domain_id="emailDomainId", email_from_name="emailFromName", email_receiving_enabled=True, + enable_voice=True, name="name", set_as_default=True, webhook_active=True, @@ -468,6 +470,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncZavudev) - email_domain_id="emailDomainId", email_from_name="emailFromName", email_receiving_enabled=True, + enable_voice=True, phone_number="phoneNumber", set_as_default=True, webhook_events=["message.queued"], @@ -561,6 +564,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncZavudev) - email_domain_id="emailDomainId", email_from_name="emailFromName", email_receiving_enabled=True, + enable_voice=True, name="name", set_as_default=True, webhook_active=True, From 549ec1d3c369113d3682c8eb499b905d954fc136 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:25:09 +0000 Subject: [PATCH 09/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/types/senders/agent_execution.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ab4c608..f031875 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-6c9214bc9b9e686d156cb81aeb3a68901168476cf5c1b1ceb4e70e92753d2eab.yml -openapi_spec_hash: be99aad1d6513b6037a3c2b07db3350c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-27815f7abab3ffa15e160c84701600150e90d8bcb10028a8797f767d59f3d5a2.yml +openapi_spec_hash: 9b8ef7af4e5aaaaf270549fa4a07a892 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/types/senders/agent_execution.py b/src/zavudev/types/senders/agent_execution.py index a418597..33e6edc 100644 --- a/src/zavudev/types/senders/agent_execution.py +++ b/src/zavudev/types/senders/agent_execution.py @@ -34,6 +34,15 @@ class AgentExecution(BaseModel): inbound_message_id: Optional[str] = FieldInfo(alias="inboundMessageId", default=None) + knowledge_chunks_used: Optional[int] = FieldInfo(alias="knowledgeChunksUsed", default=None) + """Knowledge-base chunks retrieved for this answer. + + Zero on an agent that has documents attached means the reply was not grounded in + them, which is otherwise indistinguishable from a correct answer in this record. + Absent on executions recorded before this field existed, which is not the same + as zero. + """ + response_message_id: Optional[str] = FieldInfo(alias="responseMessageId", default=None) response_text: Optional[str] = FieldInfo(alias="responseText", default=None) From 6fee13fcb0dbe55ee6257f3d9f360731da251a44 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:21:30 +0000 Subject: [PATCH 10/13] feat(api): api update --- .stats.yml | 4 +- src/zavudev/resources/invitations.py | 102 ++++++++++++++---- src/zavudev/types/invitation.py | 62 +++++++++-- src/zavudev/types/invitation_create_params.py | 27 +++-- src/zavudev/types/invitation_list_params.py | 10 +- 5 files changed, 166 insertions(+), 39 deletions(-) diff --git a/.stats.yml b/.stats.yml index f031875..c189eb8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-27815f7abab3ffa15e160c84701600150e90d8bcb10028a8797f767d59f3d5a2.yml -openapi_spec_hash: 9b8ef7af4e5aaaaf270549fa4a07a892 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-0546a3b7b196ee22e0e3dbd901c7f72918322ad668c28879f2ff3f7f2f210a9c.yml +openapi_spec_hash: 7b78d1dd981428e6d4ec20cfbc04745d config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/resources/invitations.py b/src/zavudev/resources/invitations.py index f7d2f80..7eadde9 100644 --- a/src/zavudev/resources/invitations.py +++ b/src/zavudev/resources/invitations.py @@ -54,7 +54,7 @@ def create( client_email: str | Omit = omit, client_name: str | Omit = omit, client_phone: str | Omit = omit, - connection_type: Literal["whatsapp_waba"] | Omit = omit, + connection_type: Literal["whatsapp_waba", "messenger"] | Omit = omit, expires_in_days: int | Omit = omit, phone_number_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -64,16 +64,27 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> InvitationCreateResponse: - """Create a partner invitation link for a client to connect WhatsApp. + """Create a partner invitation link for a client to connect a Meta channel. - The client - opens the returned `url` and completes Meta's embedded signup, linking an - official WhatsApp Business Account. The resulting sender is created in your - project when the client completes the flow, and the invitation transitions to + The + client opens the returned `url` and authorizes with Meta; the resulting sender + is created in your project when they finish, and the invitation transitions to `completed`. + `connectionType` picks the channel: + + - `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp + Business Account. + - `messenger`: the client picks a Facebook Page they administer; its Messenger + inbox (including Marketplace chats) is routed to Zavu. + + One invitation connects one channel — create one per channel to onboard a client + on several. `phoneNumberId` and `allowedPhoneCountries` apply to `whatsapp_waba` + only. + Args: - allowed_phone_countries: ISO country codes for allowed phone numbers. + allowed_phone_countries: ISO country codes for allowed phone numbers. Only valid when `connectionType` is + `whatsapp_waba` — sending it with `messenger` returns 400. client_email: Email of the client being invited. @@ -81,13 +92,26 @@ def create( client_phone: Phone number of the client in E.164 format. - connection_type: How the client connects WhatsApp. `whatsapp_waba` (default) runs Meta's embedded - signup to link an official WhatsApp Business Account. + connection_type: Which Meta channel the client connects, and how. + + - `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp + Business Account. Accepts `phoneNumberId` and `allowedPhoneCountries`. + - `messenger`: the client authorizes with Facebook and picks a Facebook Page + they administer. The Page's Messenger inbox — including Marketplace chats — is + routed to Zavu. They must be an admin of at least one Page. A Page can only be + connected to one Zavu project at a time: if the client picks a Page that + another project already connected, the newer connection wins and the older one + is disconnected. + + One invitation connects one channel. To onboard a client on several channels, + create one invitation per channel; each completes into its own sender. expires_in_days: Number of days until the invitation expires. phone_number_id: ID of a Zavu phone number to pre-assign for WhatsApp registration. If provided, - the client will use this number instead of their own. + the client will use this number instead of their own. Only valid when + `connectionType` is `whatsapp_waba` — sending it with `messenger` returns 400, + since a Facebook Page has no phone number. extra_headers: Send extra headers @@ -155,7 +179,7 @@ def list( *, cursor: str | Omit = omit, limit: int | Omit = omit, - status: Literal["pending", "in_progress", "completed", "expired", "cancelled"] | Omit = omit, + status: Literal["pending", "in_progress", "completed", "expired", "cancelled", "failed"] | 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, @@ -169,6 +193,11 @@ def list( Args: status: Current status of the partner invitation. + `failed` means the client started the connection and it did not finish (they + cancelled Meta's dialog, denied a permission, or abandoned the tab). A failed + invitation is still usable: the same link can be retried, and it moves back to + `in_progress` when the client tries again. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -260,7 +289,7 @@ async def create( client_email: str | Omit = omit, client_name: str | Omit = omit, client_phone: str | Omit = omit, - connection_type: Literal["whatsapp_waba"] | Omit = omit, + connection_type: Literal["whatsapp_waba", "messenger"] | Omit = omit, expires_in_days: int | Omit = omit, phone_number_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -270,16 +299,27 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> InvitationCreateResponse: - """Create a partner invitation link for a client to connect WhatsApp. + """Create a partner invitation link for a client to connect a Meta channel. - The client - opens the returned `url` and completes Meta's embedded signup, linking an - official WhatsApp Business Account. The resulting sender is created in your - project when the client completes the flow, and the invitation transitions to + The + client opens the returned `url` and authorizes with Meta; the resulting sender + is created in your project when they finish, and the invitation transitions to `completed`. + `connectionType` picks the channel: + + - `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp + Business Account. + - `messenger`: the client picks a Facebook Page they administer; its Messenger + inbox (including Marketplace chats) is routed to Zavu. + + One invitation connects one channel — create one per channel to onboard a client + on several. `phoneNumberId` and `allowedPhoneCountries` apply to `whatsapp_waba` + only. + Args: - allowed_phone_countries: ISO country codes for allowed phone numbers. + allowed_phone_countries: ISO country codes for allowed phone numbers. Only valid when `connectionType` is + `whatsapp_waba` — sending it with `messenger` returns 400. client_email: Email of the client being invited. @@ -287,13 +327,26 @@ async def create( client_phone: Phone number of the client in E.164 format. - connection_type: How the client connects WhatsApp. `whatsapp_waba` (default) runs Meta's embedded - signup to link an official WhatsApp Business Account. + connection_type: Which Meta channel the client connects, and how. + + - `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp + Business Account. Accepts `phoneNumberId` and `allowedPhoneCountries`. + - `messenger`: the client authorizes with Facebook and picks a Facebook Page + they administer. The Page's Messenger inbox — including Marketplace chats — is + routed to Zavu. They must be an admin of at least one Page. A Page can only be + connected to one Zavu project at a time: if the client picks a Page that + another project already connected, the newer connection wins and the older one + is disconnected. + + One invitation connects one channel. To onboard a client on several channels, + create one invitation per channel; each completes into its own sender. expires_in_days: Number of days until the invitation expires. phone_number_id: ID of a Zavu phone number to pre-assign for WhatsApp registration. If provided, - the client will use this number instead of their own. + the client will use this number instead of their own. Only valid when + `connectionType` is `whatsapp_waba` — sending it with `messenger` returns 400, + since a Facebook Page has no phone number. extra_headers: Send extra headers @@ -361,7 +414,7 @@ def list( *, cursor: str | Omit = omit, limit: int | Omit = omit, - status: Literal["pending", "in_progress", "completed", "expired", "cancelled"] | Omit = omit, + status: Literal["pending", "in_progress", "completed", "expired", "cancelled", "failed"] | 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, @@ -375,6 +428,11 @@ def list( Args: status: Current status of the partner invitation. + `failed` means the client started the connection and it did not finish (they + cancelled Meta's dialog, denied a permission, or abandoned the tab). A failed + invitation is still usable: the same link can be retried, and it moves back to + `in_progress` when the client tries again. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/zavudev/types/invitation.py b/src/zavudev/types/invitation.py index 06f1b12..2ae8af7 100644 --- a/src/zavudev/types/invitation.py +++ b/src/zavudev/types/invitation.py @@ -8,7 +8,27 @@ from .._models import BaseModel -__all__ = ["Invitation"] +__all__ = ["Invitation", "ConnectedAccount"] + + +class ConnectedAccount(BaseModel): + """The account the client linked, populated once the invitation is `completed`. + + Null before that. Use it to show the partner what was connected without fetching the sender. + """ + + id: str + """ + Provider-side identifier: the WhatsApp phone number ID, or the Facebook Page ID. + """ + + channel: Literal["whatsapp", "messenger"] + + name: Optional[str] = None + """ + Display name of the connected account: the WhatsApp verified name, or the + Facebook Page name. + """ class Invitation(BaseModel): @@ -21,8 +41,14 @@ class Invitation(BaseModel): expires_at: datetime = FieldInfo(alias="expiresAt") - status: Literal["pending", "in_progress", "completed", "expired", "cancelled"] - """Current status of the partner invitation.""" + status: Literal["pending", "in_progress", "completed", "expired", "cancelled", "failed"] + """Current status of the partner invitation. + + `failed` means the client started the connection and it did not finish (they + cancelled Meta's dialog, denied a permission, or abandoned the tab). A failed + invitation is still usable: the same link can be retried, and it moves back to + `in_progress` when the client tries again. + """ updated_at: datetime = FieldInfo(alias="updatedAt") @@ -37,14 +63,36 @@ class Invitation(BaseModel): completed_at: Optional[datetime] = FieldInfo(alias="completedAt", default=None) - connection_type: Optional[Literal["whatsapp_waba"]] = FieldInfo(alias="connectionType", default=None) + connected_account: Optional[ConnectedAccount] = FieldInfo(alias="connectedAccount", default=None) + """The account the client linked, populated once the invitation is `completed`. + + Null before that. Use it to show the partner what was connected without fetching + the sender. """ - How the client connects WhatsApp: `whatsapp_waba` (official Cloud API via - embedded signup). + + connection_type: Optional[Literal["whatsapp_waba", "messenger"]] = FieldInfo(alias="connectionType", default=None) + """ + Which Meta channel the client connects: `whatsapp_waba` (official WhatsApp Cloud + API via embedded signup) or `messenger` (a Facebook Page's Messenger inbox, + including Marketplace chats). + """ + + failed_at: Optional[datetime] = FieldInfo(alias="failedAt", default=None) + + failure_reason: Optional[str] = FieldInfo(alias="failureReason", default=None) + """Stable code for why the last attempt failed, present when `status` is `failed`. + + Values include `fb_cancelled` (client closed Meta's dialog), `fb_not_authorized` + (permission denied), `signup_abandoned` (started but never finished), + `meta_no_pages` (the client administers no Facebook Page), and `internal_error`. + Treat unknown codes as a generic failure. """ phone_number_id: Optional[str] = FieldInfo(alias="phoneNumberId", default=None) - """ID of a pre-assigned Zavu phone number for WhatsApp registration.""" + """ID of a pre-assigned Zavu phone number for WhatsApp registration. + + Always null for `messenger` invitations. + """ sender_id: Optional[str] = FieldInfo(alias="senderId", default=None) """ID of the sender created when invitation is completed.""" diff --git a/src/zavudev/types/invitation_create_params.py b/src/zavudev/types/invitation_create_params.py index bc70240..8ca2040 100644 --- a/src/zavudev/types/invitation_create_params.py +++ b/src/zavudev/types/invitation_create_params.py @@ -12,7 +12,11 @@ class InvitationCreateParams(TypedDict, total=False): allowed_phone_countries: Annotated[SequenceNotStr[str], PropertyInfo(alias="allowedPhoneCountries")] - """ISO country codes for allowed phone numbers.""" + """ISO country codes for allowed phone numbers. + + Only valid when `connectionType` is `whatsapp_waba` — sending it with + `messenger` returns 400. + """ client_email: Annotated[str, PropertyInfo(alias="clientEmail")] """Email of the client being invited.""" @@ -23,11 +27,20 @@ class InvitationCreateParams(TypedDict, total=False): client_phone: Annotated[str, PropertyInfo(alias="clientPhone")] """Phone number of the client in E.164 format.""" - connection_type: Annotated[Literal["whatsapp_waba"], PropertyInfo(alias="connectionType")] - """How the client connects WhatsApp. + connection_type: Annotated[Literal["whatsapp_waba", "messenger"], PropertyInfo(alias="connectionType")] + """Which Meta channel the client connects, and how. + + - `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp + Business Account. Accepts `phoneNumberId` and `allowedPhoneCountries`. + - `messenger`: the client authorizes with Facebook and picks a Facebook Page + they administer. The Page's Messenger inbox — including Marketplace chats — is + routed to Zavu. They must be an admin of at least one Page. A Page can only be + connected to one Zavu project at a time: if the client picks a Page that + another project already connected, the newer connection wins and the older one + is disconnected. - `whatsapp_waba` (default) runs Meta's embedded signup to link an official - WhatsApp Business Account. + One invitation connects one channel. To onboard a client on several channels, + create one invitation per channel; each completes into its own sender. """ expires_in_days: Annotated[int, PropertyInfo(alias="expiresInDays")] @@ -36,5 +49,7 @@ class InvitationCreateParams(TypedDict, total=False): phone_number_id: Annotated[str, PropertyInfo(alias="phoneNumberId")] """ID of a Zavu phone number to pre-assign for WhatsApp registration. - If provided, the client will use this number instead of their own. + If provided, the client will use this number instead of their own. Only valid + when `connectionType` is `whatsapp_waba` — sending it with `messenger` returns + 400, since a Facebook Page has no phone number. """ diff --git a/src/zavudev/types/invitation_list_params.py b/src/zavudev/types/invitation_list_params.py index ba936b6..c4679bd 100644 --- a/src/zavudev/types/invitation_list_params.py +++ b/src/zavudev/types/invitation_list_params.py @@ -12,5 +12,11 @@ class InvitationListParams(TypedDict, total=False): limit: int - status: Literal["pending", "in_progress", "completed", "expired", "cancelled"] - """Current status of the partner invitation.""" + status: Literal["pending", "in_progress", "completed", "expired", "cancelled", "failed"] + """Current status of the partner invitation. + + `failed` means the client started the connection and it did not finish (they + cancelled Meta's dialog, denied a permission, or abandoned the tab). A failed + invitation is still usable: the same link can be retried, and it moves back to + `in_progress` when the client tries again. + """ From da46becf9b4af3db30dc36fdfb7bedc27c791ec2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 03:51:34 +0000 Subject: [PATCH 11/13] feat(api): api update --- .stats.yml | 4 ++-- src/zavudev/types/message.py | 8 ++++++++ src/zavudev/types/message_type.py | 1 + src/zavudev/types/template.py | 2 +- src/zavudev/types/template_create_params.py | 16 +++++++++++++--- tests/api_resources/test_templates.py | 4 ++-- 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index c189eb8..152642f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-0546a3b7b196ee22e0e3dbd901c7f72918322ad668c28879f2ff3f7f2f210a9c.yml -openapi_spec_hash: 7b78d1dd981428e6d4ec20cfbc04745d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-693e8d4531d22f9dae85ab2fdf2594d7c909e14c12818c39ce84ebfd9bfd8d27.yml +openapi_spec_hash: 5e53c9310cee5f9f6467e989f92cfa04 config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/types/message.py b/src/zavudev/types/message.py index 19047d0..c7be492 100644 --- a/src/zavudev/types/message.py +++ b/src/zavudev/types/message.py @@ -32,6 +32,14 @@ class Message(BaseModel): 1024 characters) and the button label is fixed by WhatsApp. The recipient's answer arrives as an inbound `location` message whose `content.replyToMessageId` is the ID of the request. + + `request_contact_info` asks the recipient to share their phone number and is + WhatsApp-only. Like `location_request` it takes no `content` object — the prompt + goes in `text` (max 1024 characters) and WhatsApp renders a fixed **Share + Contact Info** button. The answer arrives as an inbound `contact` message. Use + it to recover the phone number of a contact who adopted a WhatsApp username and + is only known by their business-scoped user ID (BSUID); when they share it, Zavu + automatically links the phone number to that contact. """ status: MessageStatus diff --git a/src/zavudev/types/message_type.py b/src/zavudev/types/message_type.py index 7a886a7..6d01022 100644 --- a/src/zavudev/types/message_type.py +++ b/src/zavudev/types/message_type.py @@ -16,6 +16,7 @@ "buttons", "list", "cta_url", + "request_contact_info", "location_request", "reaction", "template", diff --git a/src/zavudev/types/template.py b/src/zavudev/types/template.py index 0e74d39..4ad093b 100644 --- a/src/zavudev/types/template.py +++ b/src/zavudev/types/template.py @@ -32,7 +32,7 @@ class Button(BaseModel): text: Optional[str] = None - type: Optional[Literal["quick_reply", "url", "phone", "otp"]] = None + type: Optional[Literal["quick_reply", "url", "phone", "otp", "request_contact_info"]] = None url: Optional[str] = None diff --git a/src/zavudev/types/template_create_params.py b/src/zavudev/types/template_create_params.py index 236a8c7..79e1e38 100644 --- a/src/zavudev/types/template_create_params.py +++ b/src/zavudev/types/template_create_params.py @@ -54,9 +54,12 @@ class TemplateCreateParams(TypedDict, total=False): class Button(TypedDict, total=False): - text: Required[str] - - type: Required[Literal["quick_reply", "url", "phone", "otp"]] + type: Required[Literal["quick_reply", "url", "phone", "otp", "request_contact_info"]] + """ + `request_contact_info` renders a fixed **Share Contact Info** button that asks + the recipient to share their phone number — useful when a contact adopted a + WhatsApp username and you only know their BSUID. It takes no other fields. + """ example: str """Sample value Meta uses to review templates with a dynamic URL button. @@ -79,6 +82,13 @@ class Button(TypedDict, total=False): signature_hash: Annotated[str, PropertyInfo(alias="signatureHash")] """Android app signature hash. Required for ONE_TAP buttons.""" + text: str + """Button label. + + Required for every type except `request_contact_info`, whose label is fixed by + WhatsApp. + """ + url: str """Button destination. diff --git a/tests/api_resources/test_templates.py b/tests/api_resources/test_templates.py index 571d946..08b20cc 100644 --- a/tests/api_resources/test_templates.py +++ b/tests/api_resources/test_templates.py @@ -40,13 +40,13 @@ def test_method_create_with_all_params(self, client: Zavudev) -> None: add_security_recommendation=True, buttons=[ { - "text": "text", "type": "quick_reply", "example": "ORD-12345", "otp_type": "COPY_CODE", "package_name": "packageName", "phone_number": "phoneNumber", "signature_hash": "signatureHash", + "text": "text", "url": "https://example.com", } ], @@ -295,13 +295,13 @@ async def test_method_create_with_all_params(self, async_client: AsyncZavudev) - add_security_recommendation=True, buttons=[ { - "text": "text", "type": "quick_reply", "example": "ORD-12345", "otp_type": "COPY_CODE", "package_name": "packageName", "phone_number": "phoneNumber", "signature_hash": "signatureHash", + "text": "text", "url": "https://example.com", } ], From d9c58f8db4b81b234189ee95a1e2fcf6405e032f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:51:49 +0000 Subject: [PATCH 12/13] feat(api): api update --- .stats.yml | 4 +-- src/zavudev/resources/messages.py | 34 ++++++++++++++----- src/zavudev/resources/number_10dlc/brands.py | 12 +++++-- .../number_10dlc/campaigns/campaigns.py | 14 ++++++-- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/.stats.yml b/.stats.yml index 152642f..9a11071 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-693e8d4531d22f9dae85ab2fdf2594d7c909e14c12818c39ce84ebfd9bfd8d27.yml -openapi_spec_hash: 5e53c9310cee5f9f6467e989f92cfa04 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-69a49371271acbb8de23d4d5336746b1487a5fb9b30189ec74f6a74a8a487951.yml +openapi_spec_hash: 05122bf62f2a0ba6f1948eb97f1af7fb config_hash: 70a26cd21f0ca1b34242677b831603db diff --git a/src/zavudev/resources/messages.py b/src/zavudev/resources/messages.py index 4410fcc..c4ea702 100644 --- a/src/zavudev/resources/messages.py +++ b/src/zavudev/resources/messages.py @@ -216,10 +216,19 @@ def send( - Window opens when the user messages you first - Use template messages to initiate conversations outside the window - **Daily limits:** - - - Unverified accounts: 200 messages per channel per day - - Complete KYC verification to increase limits to 10,000/day + **Plan allowances and email billing:** + + - WhatsApp, Telegram, Instagram and Messenger share an allowance of 2,000 + messages per month on Free. Over it, sends return 429 with code + `a2p_limit_exceeded` and upgrade details; the counter resets on the 1st of + each month. Paid plans have no message caps + - Email is billed from your prepaid balance in 1,000-message blocks: $0.40 per + 1,000 transactional emails, $0.80 per 1,000 marketing (broadcast) emails. A + block is charged when your monthly count crosses each 1,000 boundary, and at + zero balance email sends return 402 with code `insufficient_balance`. Free + teams start with $2 of credit and additionally cap at 3,000 emails/month and + 100/day. Teams on earlier plans keep their original email quotas instead + - SMS and voice are billed per message from your balance on every plan **Email recipient pre-flight:** Email messages are validated automatically before dispatch. Sends that would be a guaranteed hard bounce are failed instead @@ -530,10 +539,19 @@ async def send( - Window opens when the user messages you first - Use template messages to initiate conversations outside the window - **Daily limits:** - - - Unverified accounts: 200 messages per channel per day - - Complete KYC verification to increase limits to 10,000/day + **Plan allowances and email billing:** + + - WhatsApp, Telegram, Instagram and Messenger share an allowance of 2,000 + messages per month on Free. Over it, sends return 429 with code + `a2p_limit_exceeded` and upgrade details; the counter resets on the 1st of + each month. Paid plans have no message caps + - Email is billed from your prepaid balance in 1,000-message blocks: $0.40 per + 1,000 transactional emails, $0.80 per 1,000 marketing (broadcast) emails. A + block is charged when your monthly count crosses each 1,000 boundary, and at + zero balance email sends return 402 with code `insufficient_balance`. Free + teams start with $2 of credit and additionally cap at 3,000 emails/month and + 100/day. Teams on earlier plans keep their original email quotas instead + - SMS and voice are billed per message from your balance on every plan **Email recipient pre-flight:** Email messages are validated automatically before dispatch. Sends that would be a guaranteed hard bounce are failed instead diff --git a/src/zavudev/resources/number_10dlc/brands.py b/src/zavudev/resources/number_10dlc/brands.py index 920bb6e..128aa5c 100644 --- a/src/zavudev/resources/number_10dlc/brands.py +++ b/src/zavudev/resources/number_10dlc/brands.py @@ -354,7 +354,11 @@ def submit( """Submit a draft brand to The Campaign Registry (TCR) for vetting. The brand must - be in draft status. A $35 registration fee is charged from your balance. + be in draft status. TCR's one-time $4 brand registration fee is charged from + your balance at submission (passed through at cost) and refunded if the carrier + rejects the registration. A team that already paid this fee through the + compliance flow is not charged again. Campaign registration is billed separately + when a campaign is submitted. Args: extra_headers: Send extra headers @@ -735,7 +739,11 @@ async def submit( """Submit a draft brand to The Campaign Registry (TCR) for vetting. The brand must - be in draft status. A $35 registration fee is charged from your balance. + be in draft status. TCR's one-time $4 brand registration fee is charged from + your balance at submission (passed through at cost) and refunded if the carrier + rejects the registration. A team that already paid this fee through the + compliance flow is not charged again. Campaign registration is billed separately + when a campaign is submitted. Args: extra_headers: Send extra headers diff --git a/src/zavudev/resources/number_10dlc/campaigns/campaigns.py b/src/zavudev/resources/number_10dlc/campaigns/campaigns.py index 1ffecb5..fa73cd5 100644 --- a/src/zavudev/resources/number_10dlc/campaigns/campaigns.py +++ b/src/zavudev/resources/number_10dlc/campaigns/campaigns.py @@ -319,7 +319,12 @@ def submit( """Submit a draft campaign for carrier review. The campaign must be in draft status - and its brand must be verified. + and its brand must be verified. TCR's one-time registration fee is charged from + your balance at submission ($15 for standard use cases, $2 for LOW_VOLUME), + passed through at cost and refunded if the carrier rejects it. Once approved, + the campaign's monthly TCR fee ($10 standard, $2 LOW_VOLUME) is charged from + your balance while the campaign is active — see registrationCostCents and + monthlyFeeCents on the campaign object. Args: extra_headers: Send extra headers @@ -660,7 +665,12 @@ async def submit( """Submit a draft campaign for carrier review. The campaign must be in draft status - and its brand must be verified. + and its brand must be verified. TCR's one-time registration fee is charged from + your balance at submission ($15 for standard use cases, $2 for LOW_VOLUME), + passed through at cost and refunded if the carrier rejects it. Once approved, + the campaign's monthly TCR fee ($10 standard, $2 LOW_VOLUME) is charged from + your balance while the campaign is active — see registrationCostCents and + monthlyFeeCents on the campaign object. Args: extra_headers: Send extra headers From ac5343323dbda9b5e264a0ab9fcfa9e6d0903365 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:52:15 +0000 Subject: [PATCH 13/13] release: 0.47.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- src/zavudev/_version.py | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 563004f..141e7cd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.46.0" + ".": "0.47.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ffdcb..c559515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 0.47.0 (2026-08-02) + +Full Changelog: [v0.46.0...v0.47.0](https://github.com/zavudev/sdk-python/compare/v0.46.0...v0.47.0) + +### Features + +* **api:** api update ([d9c58f8](https://github.com/zavudev/sdk-python/commit/d9c58f8db4b81b234189ee95a1e2fcf6405e032f)) +* **api:** api update ([da46bec](https://github.com/zavudev/sdk-python/commit/da46becf9b4af3db30dc36fdfb7bedc27c791ec2)) +* **api:** api update ([6fee13f](https://github.com/zavudev/sdk-python/commit/6fee13fcb0dbe55ee6257f3d9f360731da251a44)) +* **api:** api update ([549ec1d](https://github.com/zavudev/sdk-python/commit/549ec1d3c369113d3682c8eb499b905d954fc136)) +* **api:** api update ([1cafed2](https://github.com/zavudev/sdk-python/commit/1cafed2e21dfccb90a4ab09b98525be28d7481c9)) +* **api:** api update ([acf9e00](https://github.com/zavudev/sdk-python/commit/acf9e008feae130193c9b12759c3ac6801c0b58e)) +* **api:** api update ([eab4046](https://github.com/zavudev/sdk-python/commit/eab4046ff43c57b684917c7fc107f9c1ca8d582e)) +* **api:** api update ([5954d05](https://github.com/zavudev/sdk-python/commit/5954d051a7563b2b59971a37cb465813c929b088)) +* **api:** api update ([5a5e327](https://github.com/zavudev/sdk-python/commit/5a5e32773275af52862c9e4d00e98f00052ecc5f)) +* **api:** api update ([fe4c991](https://github.com/zavudev/sdk-python/commit/fe4c991998a0838466edce4e46e61b1fb212a470)) +* **api:** api update ([651eca7](https://github.com/zavudev/sdk-python/commit/651eca7024525cb98251498dab6939f3b9d363a6)) +* **api:** api update ([5cded74](https://github.com/zavudev/sdk-python/commit/5cded745f3a5a725db610b09a4c456fad434c3a5)) + ## 0.46.0 (2026-07-23) Full Changelog: [v0.45.0...v0.46.0](https://github.com/zavudev/sdk-python/compare/v0.45.0...v0.46.0) diff --git a/pyproject.toml b/pyproject.toml index 640d48f..41c18af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "zavudev" -version = "0.46.0" +version = "0.47.0" description = "The official Python library for the zavudev API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/zavudev/_version.py b/src/zavudev/_version.py index 724adae..bd5f0f6 100644 --- a/src/zavudev/_version.py +++ b/src/zavudev/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "zavudev" -__version__ = "0.46.0" # x-release-please-version +__version__ = "0.47.0" # x-release-please-version