Python SDK

The official AgentPhone Python library provides convenient access to the AgentPhone API with both synchronous and asynchronous clients.

PyPI

Installation

pip install agentphone

For async support:

pip install agentphone[async]

Quick start

from agentphone import AgentPhone
client = AgentPhone(api_key="YOUR_API_KEY")
# Create an agent
agent = client.agents.create(name="Support Bot")
# Buy a number and attach it
number = client.numbers.buy(country="US", agent_id=agent.id)
# Make an AI-powered call
call = 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 AsyncAgentPhone
async 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 agents
agents = client.agents.list(limit=20, offset=0)
# Create an agent with voice configuration
agent = client.agents.create(
name="Support Bot",
description="Handles customer inquiries",
voice_mode="hosted", # "hosted" for built-in AI, "webhook" for custom
system_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 here
voicemail_message="Leave a message after the beep.",
)
# Get agent details
agent = client.agents.get(agent_id="agt_abc123")
# Update an agent
agent = client.agents.update(
agent_id="agt_abc123",
name="Updated Bot",
system_prompt="New prompt here.",
)
# Delete an agent
client.agents.delete(agent_id="agt_abc123")
# Attach / detach a number
client.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 agent
calls = client.agents.list_calls(agent_id="agt_abc123", limit=20)
convos = client.agents.list_conversations(agent_id="agt_abc123", limit=20)
# List available voices
voices = 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 webhook
webhook = client.agents.get_webhook(agent_id="agt_abc123")
client.agents.delete_webhook(agent_id="agt_abc123")
# View deliveries and send a test event
deliveries = client.agents.list_webhook_deliveries(agent_id="agt_abc123", limit=50)
client.agents.test_webhook(agent_id="agt_abc123")

Numbers

# List numbers
numbers = client.numbers.list(limit=20, offset=0)
# Buy a new number
number = client.numbers.buy(
country="US",
area_code="415", # optional, US/CA only
agent_id="agt_abc123", # optional: attach immediately
)
# Get messages for a number
messages = client.numbers.get_messages(number_id="num_xyz789", limit=50)
# List calls for a number
calls = 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 iMessage
client.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 contacts
contacts = client.contacts.list(limit=50, search="Alice")
# Create a contact
contact = client.contacts.create(
phone_number="+15559876543",
name="Alice Smith",
email="alice@example.com",
notes="VIP customer",
)
# Get / update / delete
contact = 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 conversations
convos = client.conversations.list(limit=20, offset=0)
# Get a conversation with messages
convo = client.conversations.get(
conversation_id="conv_abc123",
message_limit=50,
)
# Get messages with cursor pagination
messages = client.conversations.get_messages(
conversation_id="conv_abc123",
limit=50,
before="msg_older", # cursor pagination
after="msg_newer",
)
# Update conversation metadata
convo = client.conversations.update(
conversation_id="conv_abc123",
metadata={"priority": "high", "assigned_to": "team-a"},
)

Calls

# List calls with optional filters
calls = client.calls.list(
limit=20,
status="completed", # optional filter
direction="outbound", # optional filter
type="conversation", # optional filter
search="+1415", # optional search
)
# Get a call with transcript
call = 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 number
voice="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 separately
transcript = client.calls.get_transcript(call_id="call_abc123")
# Stream transcript via Server-Sent Events
for 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 config
webhook = client.webhooks.get()
# Set or update webhook
webhook = client.webhooks.set(
url="https://your-server.com/webhook",
context_limit=10, # 0-50 recent messages in payloads
timeout=30, # response timeout in seconds (5-120)
)
print(webhook.secret) # save this!
# View delivery history
deliveries = client.webhooks.list_deliveries(limit=50)
# Get delivery statistics
stats = client.webhooks.get_delivery_stats(hours=24)
print(f"Success rate: {stats.success_rate}%")
all_time = client.webhooks.get_all_time_stats()
# Test webhook
client.webhooks.test(agent_id="agt_abc123") # optional: test for specific agent
# Delete webhook
client.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 breakdown
daily = client.usage.get_daily(days=30)
for day in daily.data:
print(f"{day.date}: {day.messages} msgs, {day.calls} calls")
# Monthly breakdown
monthly = 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 only
try:
verify_webhook(payload, signature, secret, timestamp)
except WebhookVerificationError:
print("Invalid or stale signature")
# Option 2: Verify + parse into a typed event
event = 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 context
print(f" [{item.direction}] {item.content}")
print(event.conversation_state) # custom metadata

Flask example

from flask import Flask, request, jsonify
from agentphone import construct_event
app = 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 caller
return 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,
)