Skip to navigation

Python SDK

The official AgentPhone Python library provides convenient access to the AgentPhone API with both synchronous and asynchronous clients. These examples target published agentphone==0.20.1. Python method names and snake_case arguments differ from the TypeScript SDK and REST JSON fields.

PyPI

Installation

pip install agentphone

For async support:

pip install 'agentphone[async]'

Set AGENTPHONE_API_KEY in the server process before running examples.

export AGENTPHONE_API_KEY="YOUR_API_KEY"

Quick start

import os
from agentphone import AgentPhone
client = AgentPhone(api_key=os.environ["AGENTPHONE_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

import asyncio
import os
from agentphone import AsyncAgentPhone
async def main():
async with AsyncAgentPhone(api_key=os.environ["AGENTPHONE_API_KEY"]) as client:
agents = await client.agents.list()
for agent in agents.data:
print(agent.id, agent.name)
asyncio.run(main())

The quick start buys a number and places a real call. Read pricing and outbound calling requirements and use a recipient you control. The SDK does not expose every REST field; use authenticated HTTP for unsupported operations and sub-account headers.

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_urls=["https://example.com/image.png"], # optional; replace with a reachable file
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="2026-10-01T12:00:00Z", # timestamp, not a message ID; use before OR after
)
# 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="pstn", # 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(channel="sms")
# For a specific agent instead:
client.agents.test_webhook(agent_id="agt_abc123")
# Delete webhook
client.webhooks.delete()

Usage

# Get counts and number limits (not a plan or balance)
usage = client.usage.get()
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

The typed event model in 0.20.1 includes a subset of the webhook fields. For data.messageId, group context, attachments, reactions, or contact sharing, call verify_webhook() first and then parse the raw JSON so those fields remain available. Use data.group.groupId when replying to a group; do not route a group response to the individual sender.

Flask example

Save the signing secret returned when you configure the webhook, then run export AGENTPHONE_WEBHOOK_SECRET="YOUR_WEBHOOK_SIGNING_SECRET" in the server environment before starting this app. This is separate from AGENTPHONE_API_KEY.

import os
from flask import Flask, request, jsonify
from agentphone import construct_event, WebhookVerificationError
app = Flask(__name__)
secret = os.environ["AGENTPHONE_WEBHOOK_SECRET"]
@app.post("/webhook")
def webhook():
try:
event = construct_event(
payload=request.get_data(),
signature=request.headers.get("X-Webhook-Signature", ""),
secret=secret,
timestamp=request.headers.get("X-Webhook-Timestamp", ""),
)
except WebhookVerificationError:
return "Invalid signature", 401
except (ValueError, TypeError, KeyError, AttributeError):
return "Invalid event", 400
if event.event == "agent.message" and event.channel == "voice":
return jsonify({"text": f"You said: {event.data.transcript}"})
# Text events need a separate API send to reply. Deduplicate queued work.
return "OK", 200

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,
)