Install
Requires Python 3.10+.
Quickstart
import os
import yotel
client = yotel.Client(api_key=os.environ["YOTEL_KEY"])
# Create campaign
c = client.campaigns.create(
name="Q4 outreach",
dial_mode="predictive",
predictive_target_abandon=0.02,
)
# Push lead
lead = client.leads.create(
campaign_id=c.id,
phone="9876543210",
name="Priya",
email="priya@example.com",
)
# Bulk push (up to 500)
result = client.leads.bulk(
campaign_id=c.id,
leads=[
{"phone": "9876543211"},
{"phone": "9876543212", "name": "Arjun"},
],
)
print(result.valid, "added,", result.duplicates, "skipped")
Verifying webhooks
from yotel.webhook import verify_and_parse, InvalidSignature
# In your FastAPI / Flask / Django handler:
raw = await request.body()
try:
event = verify_and_parse(
raw,
headers=dict(request.headers),
signing_secret=os.environ["YOTEL_WEBHOOK_SECRET"],
)
except InvalidSignature:
return Response(status_code=401)
# event.event_type is typed: 'call.ended' | 'campaign.paused' | ...
if event.event_type == "call.ended":
call_id = event.data["call_id"]
...
Rate-limit handling
By default the client retries 429s up to 3 times, respecting
Retry-After:
# Default — let the SDK retry
client = yotel.Client(api_key="...")
# Disable — handle 429 yourself
client = yotel.Client(api_key="...", retry_on_429=False)
Async
import yotel
async with yotel.AsyncClient(api_key="...") as client:
c = await client.campaigns.create(name="...", dial_mode="predictive")
Both sync and async clients ship in the same package.
Error handling
from yotel import (
AuthenticationError,
RateLimitError,
ValidationError,
APIError,
)
try:
client.leads.create(campaign_id="c-1", phone="bad")
except ValidationError as e:
print(e.errors) # Pydantic-shaped field errors
except AuthenticationError:
...
except RateLimitError as e:
print("retry after", e.retry_after)
except APIError as e:
print(e.status_code, e.detail)
OAuth helpers
For partner apps that act on behalf of individual Yotel users, the
SDK ships PKCE-ready OAuth helpers in yotel.oauth:
from yotel.oauth import AuthorizationCodeFlow, generate_code_verifier
flow = AuthorizationCodeFlow(
client_id="sfdc_yotel",
client_secret=os.environ["YOTEL_CLIENT_SECRET"],
redirect_uri="https://app.example/cb",
scopes=["campaigns:read", "openid", "offline_access"],
)
verifier = generate_code_verifier()
url = flow.build_authorize_url(state="csrf-1", code_verifier=verifier)
# ... redirect user, callback handler ...
tokens = flow.exchange_code(code=code, code_verifier=verifier)
tokens = tokens.refresh() # rotated refresh_token
For the server-to-server JWT Bearer grant, install the oauth extra:
pip install "yotel[oauth]"
See OAuth 2.0 for the full flow reference.
Recordings
Access call recordings — signed URLs, raw bytes, or streamed to disk.
# Get a signed URL (7-day expiry)
recording = client.recordings.get_signed_url("call_id")
print(recording["url"], recording["stereo"])
# Download to disk
path = client.recordings.download("call_id", "recording.wav")
# Load into memory
audio = client.recordings.fetch_bytes("call_id")
get_signed_url requires calls:read scope. fetch_bytes and
download require the separate recordings:download scope.
See Recordings API for response details.
Voice agents
Full CRUD for voice agent routing aliases.
# Create
va = client.voice_agents.create(
name="Support bot",
ws_url="wss://ai.example.com/ws",
max_concurrent=50,
)
# List / Get
all_agents = client.voice_agents.list()
one = client.voice_agents.get(va.id)
# Update (PATCH)
client.voice_agents.update(va.id, max_concurrent=100)
# Archive (soft delete) or hard delete
client.voice_agents.archive(va.id)
client.voice_agents.delete(va.id)
# Tenant default
client.voice_agents.set_as_default(va.id)
default_id = client.voice_agents.get_default()
See Voice agents CRUD and
Voice agent defaults.
AI sessions
Control active voice AI sessions with typed verb methods.
# Transfer call to a human agent
client.ai_sessions.transfer(
call_id="call_abc",
destination_type="agent",
destination="agent_xyz",
callback_token="yt_cb_...",
)
# Hang up
client.ai_sessions.hangup("call_abc", callback_token="yt_cb_...")
# Set disposition
client.ai_sessions.set_disposition(
"call_abc",
disposition="interested",
notes="Wants premium plan",
callback_token="yt_cb_...",
)
# Get current call state
state = client.ai_sessions.get_call_state("call_abc", callback_token="yt_cb_...")
All 25 control verbs are available as typed methods:
transfer, hangup, log, mute, unmute, hold, unhold,
send_dtmf, play_audio, set_disposition, recording_pause,
recording_resume, get_call_state, conference_start,
conference_add, conference_remove, conference_leave,
request_supervisor, whisper, barge, monitor_start,
monitor_stop, set_lead_field, set_lead_status,
schedule_callback.
See AI sessions and the
Control API reference for verb parameters.
Additional error types
Beyond the base errors, voice agent and AI session
operations can raise:
from yotel import (
NoVoiceAgentConfigured, # 424 — no agent resolved in hierarchy
VoiceAgentInUse, # 409 — active sessions prevent deletion
MaxConcurrentExceeded, # 429 — concurrency limit reached
ConferenceMembershipRequired, # 403 — whisper/barge needs conference
AISessionTerminated, # 410 — call already ended
)
See Error reference for details.
Source
github.com/kamal-zetta/dialer/tree/main/sdks/python