Python SDK
The official AgentPhone Python library provides convenient access to the AgentPhone API with both synchronous and asynchronous clients.
Installation
pip install agentphone
For async support:
pip install agentphone[async]
Quick start
from agentphone import AgentPhoneclient = AgentPhone(api_key="YOUR_API_KEY")# Create an agentagent = client.agents.create(name="Support Bot")# Buy a number and attach itnumber = client.numbers.buy(country="US", agent_id=agent.id)# Make an AI-powered callcall = client.calls.make(agent_id=agent.id,to_number="+15559876543",system_prompt="You are a helpful support assistant.",initial_greeting="Hi, this is Support Bot!",)
Async usage
from agentphone import AsyncAgentPhoneasync with AsyncAgentPhone(api_key="YOUR_API_KEY") as client:agents = await client.agents.list()call = await client.calls.make(agent_id=agents.data[0].id,to_number="+15559876543",system_prompt="You are a helpful assistant.",)
Resources
Agents
# List all agentsagents = client.agents.list(limit=20, offset=0)# Create an agent with voice configurationagent = client.agents.create(name="Support Bot",description="Handles customer inquiries",voice_mode="hosted", # "hosted" for built-in AI, "webhook" for customsystem_prompt="You are a friendly support agent.",begin_message="Hi! How can I help?",voice="11labs-Brian", # see list_voices()transfer_number="+15551234567", # optional: transfer calls herevoicemail_message="Leave a message after the beep.",)# Get agent detailsagent = client.agents.get(agent_id="agt_abc123")# Update an agentagent = client.agents.update(agent_id="agt_abc123",name="Updated Bot",system_prompt="New prompt here.",)# Delete an agentclient.agents.delete(agent_id="agt_abc123")# Attach / detach a numberclient.agents.attach_number(agent_id="agt_abc123", number_id="num_xyz789")client.agents.detach_number(agent_id="agt_abc123", number_id="num_xyz789")# List calls and conversations for an agentcalls = client.agents.list_calls(agent_id="agt_abc123", limit=20)convos = client.agents.list_conversations(agent_id="agt_abc123", limit=20)# List available voicesvoices = client.agents.list_voices()
Per-agent webhooks
# Set a webhook for a specific agent (overrides project default)webhook = client.agents.set_webhook(agent_id="agt_abc123",url="https://your-server.com/agent-hook",context_limit=10,timeout=30,)# Get / delete an agent's webhookwebhook = client.agents.get_webhook(agent_id="agt_abc123")client.agents.delete_webhook(agent_id="agt_abc123")# View deliveries and send a test eventdeliveries = client.agents.list_webhook_deliveries(agent_id="agt_abc123", limit=50)client.agents.test_webhook(agent_id="agt_abc123")
Numbers
# List numbersnumbers = client.numbers.list(limit=20, offset=0)# Buy a new numbernumber = client.numbers.buy(country="US",area_code="415", # optional, US/CA onlyagent_id="agt_abc123", # optional: attach immediately)# Get messages for a numbermessages = client.numbers.get_messages(number_id="num_xyz789", limit=50)# List calls for a numbercalls = client.numbers.list_calls(number_id="num_xyz789", limit=20)# Release a number (irreversible)client.numbers.release(number_id="num_xyz789")
Messages
# Send an SMS or iMessageclient.messages.send(agent_id="agt_abc123",to_number="+15559876543",body="Hello from my agent!",media_url="https://example.com/image.png", # optional (MMS/iMessage)number_id="num_xyz789", # optional: send from specific number)# Send a tapback reaction (iMessage only)client.messages.react(message_id="msg_abc123",reaction="love", # love, like, dislike, laugh, emphasize, question)
Contacts
# List contactscontacts = client.contacts.list(limit=50, search="Alice")# Create a contactcontact = client.contacts.create(phone_number="+15559876543",name="Alice Smith",email="alice@example.com",notes="VIP customer",)# Get / update / deletecontact = client.contacts.get(contact_id="ct_abc123")contact = client.contacts.update(contact_id="ct_abc123", name="Alice Johnson")client.contacts.delete(contact_id="ct_abc123")
Conversations
# List all conversationsconvos = client.conversations.list(limit=20, offset=0)# Get a conversation with messagesconvo = client.conversations.get(conversation_id="conv_abc123",message_limit=50,)# Get messages with cursor paginationmessages = client.conversations.get_messages(conversation_id="conv_abc123",limit=50,before="msg_older", # cursor paginationafter="msg_newer",)# Update conversation metadataconvo = client.conversations.update(conversation_id="conv_abc123",metadata={"priority": "high", "assigned_to": "team-a"},)
Calls
# List calls with optional filterscalls = client.calls.list(limit=20,status="completed", # optional filterdirection="outbound", # optional filtertype="conversation", # optional filtersearch="+1415", # optional search)# Get a call with transcriptcall = client.calls.get(call_id="call_abc123")# Make an outbound call with built-in AI (no webhook needed)call = client.calls.make(agent_id="agt_abc123",to_number="+15559876543",system_prompt="You are a support agent helping with order inquiries.",initial_greeting="Hello! How can I help you today?",from_number_id="num_xyz789", # optional: call from specific numbervoice="11labs-Brian", # optional: override agent voice)# Make a webhook-based call (omit system_prompt, requires webhook configured)call = client.calls.make(agent_id="agt_abc123",to_number="+15559876543",initial_greeting="Hello!",)# Create a web-based call (browser)web_call = client.calls.create_web_call(agent_id="agt_abc123",metadata={"source": "dashboard"},)# Get transcript separatelytranscript = client.calls.get_transcript(call_id="call_abc123")# Stream transcript via Server-Sent Eventsfor event in client.calls.stream_transcript(call_id="call_abc123"):if event["event"] == "turn":print(f"[{event['data']['role']}] {event['data']['content']}")elif event["event"] == "ended":print(f"Call ended ({event['data']['durationSeconds']}s)")
Webhooks (project-level)
# Get webhook configwebhook = client.webhooks.get()# Set or update webhookwebhook = client.webhooks.set(url="https://your-server.com/webhook",context_limit=10, # 0-50 recent messages in payloadstimeout=30, # response timeout in seconds (5-120))print(webhook.secret) # save this!# View delivery historydeliveries = client.webhooks.list_deliveries(limit=50)# Get delivery statisticsstats = client.webhooks.get_delivery_stats(hours=24)print(f"Success rate: {stats.success_rate}%")all_time = client.webhooks.get_all_time_stats()# Test webhookclient.webhooks.test(agent_id="agt_abc123") # optional: test for specific agent# Delete webhookclient.webhooks.delete()
Usage
# Get current usage summary (plan, limits, stats)usage = client.usage.get()print(f"Plan: {usage.plan.name}")print(f"Numbers: {usage.numbers.used}/{usage.numbers.limit}")print(f"Messages (30d): {usage.stats.messages_last_30d}")print(f"Calls (30d): {usage.stats.calls_last_30d}")# Daily breakdowndaily = client.usage.get_daily(days=30)for day in daily.data:print(f"{day.date}: {day.messages} msgs, {day.calls} calls")# Monthly breakdownmonthly = client.usage.get_monthly(months=6)
Error handling
from agentphone import (AgentPhoneError,AuthenticationError,NotFoundError,RateLimitError,)try:agent = client.agents.get(agent_id="bad-id")except NotFoundError:print("Agent not found")except AuthenticationError:print("Invalid API key")except RateLimitError:print("Too many requests")except AgentPhoneError as e:print(f"API error {e.status}: {e.message}")
Webhook verification
Each delivery is signed over "{timestamp}.{raw_body}". The signature arrives in the X-Webhook-Signature header and the timestamp in X-Webhook-Timestamp, so pass both along with the raw body and your secret. Both helpers also enforce a 5-minute replay window by default (tolerance=300; pass tolerance=None to skip it). Requires agentphone >= 0.17.0.
from agentphone import construct_event, verify_webhook, WebhookVerificationError# payload = raw request body (bytes)# signature = X-Webhook-Signature header# secret = your webhook secret# timestamp = X-Webhook-Timestamp header# Option 1: Verify signature onlytry:verify_webhook(payload, signature, secret, timestamp)except WebhookVerificationError:print("Invalid or stale signature")# Option 2: Verify + parse into a typed eventevent = construct_event(payload, signature, secret, timestamp)if event.event == "agent.message":print(event.data.message)print(event.data.from_number)print(event.channel) # "sms", "imessage", or "voice"for item in event.recent_history: # recent conversation contextprint(f" [{item.direction}] {item.content}")print(event.conversation_state) # custom metadata
Flask example
from flask import Flask, request, jsonifyfrom agentphone import construct_eventapp = Flask(__name__)@app.route("/webhook", methods=["POST"])def webhook():event = construct_event(payload=request.data,signature=request.headers.get("X-Webhook-Signature", ""),secret="your_webhook_secret",timestamp=request.headers.get("X-Webhook-Timestamp", ""),)# Respond to the callerreturn jsonify({"response": f"Got your message: {event.data.message}"})
Advanced
Context manager
with AgentPhone(api_key="YOUR_API_KEY") as client:agents = client.agents.list()# session is automatically closed
Custom base URL and timeout
client = AgentPhone(api_key="YOUR_API_KEY",base_url="https://api.agentphone.ai",timeout=30.0,)

