AgentPhone Python SDK
Official Python SDK for AgentPhone — give your AI agents real phone numbers, SMS, and voice calls.
Installation
pip install agentphone
For async support:
pip install agentphone[async]
Quickstart
import os
from agentphone import AgentPhone
client = AgentPhone(api_key=os.environ["AGENTPHONE_API_KEY"])
# Create an agent with hosted voice
agent = client.agents.create(
name="My Agent",
voice_mode="hosted",
system_prompt="Schedule appointments for a dentist office.",
voice="alloy",
)
# Buy a number and attach it
number = client.numbers.buy(country="US", agent_id=agent.id)
# Make an AI conversation call — no webhook needed
call = client.calls.make(
agent_id=agent.id,
to_number="+14155551234",
system_prompt="Schedule a dentist appointment for next Tuesday at 2pm",
initial_greeting="Hi, I'm calling to schedule an appointment.",
)
print(call.status)
Async
import os
from agentphone import AsyncAgentPhone
async with AsyncAgentPhone(api_key=os.environ["AGENTPHONE_API_KEY"]) as client:
numbers = await client.numbers.list()
call = await client.calls.make(
agent_id="agent_123",
to_number="+14155551234",
system_prompt="Ask about their return policy",
)
Resources
| Resource | Methods |
|---|---|
client.numbers |
list(), buy(), release(), get(), get_messages(), list_calls(), get_contact_card(), set_contact_card(), delete_contact_card() |
client.contacts |
list(), create(), get(), update(), delete() |
client.agents |
list(), create(), get(), update(), delete(), attach_number(), detach_number(), list_calls(), list_conversations(), list_voices(), set_webhook(), get_webhook(), delete_webhook(), list_webhook_deliveries(), test_webhook() |
client.calls |
list(), get(), make(), end(), create_web_call(), get_recording(), get_transcript(), stream_transcript() |
client.messages |
send(), react() |
client.conversations |
list(), get(), update(), get_messages(), send_typing(), set_background(), delete_background() |
client.webhooks |
get(), set(), delete(), list_deliveries(), test(), get_delivery_stats(), get_all_time_stats() |
client.usage |
get(), get_daily(), get_monthly(), get_by_number(), get_by_agent() |
client.registration |
register() |
client.sub_accounts |
list(), create(), update(), delete() |
client.location |
get(), refresh() |
Contacts
# Create a contact
contact = client.contacts.create(
phone_number="+14155551234",
name="Jane Doe",
email="jane@example.com",
notes="VIP customer",
)
# List contacts with search
contacts = client.contacts.list(search="Jane")
# Get, update, delete
contact = client.contacts.get("contact_123")
client.contacts.update("contact_123", name="Jane Smith")
client.contacts.delete("contact_123")
Agents
# Create with full voice config
agent = client.agents.create(
name="Support Bot",
description="Handles customer support",
voice_mode="hosted",
model_tier="balanced", # "turbo", "balanced", or "max"
system_prompt="Handle customer support inquiries.",
begin_message="Hello, how can I help you today?",
voice="alloy",
stt_mode="fast", # "fast" or "accurate"
)
# Update
client.agents.update(agent.id, name="Updated Bot", voice="nova")
# Delete
client.agents.delete(agent.id)
# Attach / detach numbers
client.agents.attach_number(agent.id, number_id="num_123")
client.agents.detach_number(agent.id, number_id="num_123")
# List available voices
voices = client.agents.list_voices()
for v in voices:
print(v["name"])
# Agent-specific webhooks
client.agents.set_webhook(agent.id, url="https://my-server.com/hook")
client.agents.get_webhook(agent.id)
client.agents.delete_webhook(agent.id)
client.agents.list_webhook_deliveries(agent.id)
client.agents.test_webhook(agent.id)
# List agent's calls and conversations
calls = client.agents.list_calls(agent.id)
convos = client.agents.list_conversations(agent.id)
Calls
# Webhook-based call (your server handles the conversation)
call = client.calls.make(
agent_id=agent.id,
to_number="+14155551234",
from_number_id="num_123", # optional: pick which number to call from
voice="alloy", # optional: override voice
)
# AI conversation call (no webhook needed) — pass system_prompt
call = client.calls.make(
agent_id=agent.id,
to_number="+14155551234",
system_prompt="Ask about their return policy",
initial_greeting="Hi, I'm calling about a recent order.",
from_number_id="num_123",
voice="alloy",
)
# List calls with filters
calls = client.calls.list(status="completed", direction="outbound", limit=10)
# Get call details and transcript
call = client.calls.get("call_123")
for t in call.transcripts:
print(f"[{t.created_at}] {t.transcript}")
# Dedicated transcript endpoint
transcript = client.calls.get_transcript("call_123")
# Stream transcript in real time (SSE)
for event in client.calls.stream_transcript("call_123"):
if event["event"] == "turn":
print(f"[{event['data']['role']}] {event['data']['content']}")
elif event["event"] == "ended":
print(f"Call ended — {event['data']['durationSeconds']}s")
Sending Messages
# Send an outbound SMS
client.messages.send(
agent_id="agent_123",
to_number="+14155551234",
body="Your appointment is confirmed for Tuesday at 2pm.",
)
# Send with media
client.messages.send(
agent_id="agent_123",
to_number="+14155551234",
body="Here's your receipt",
media_url="https://example.com/receipt.pdf",
)
# React to a message (iMessage only)
client.messages.react("msg_123", reaction="love")
Conversations & SMS
# List conversations
convos = client.conversations.list()
# Get conversation with messages
convo = client.conversations.get("conv_123", message_limit=100)
for msg in convo.messages:
print(f"{msg.from_number}: {msg.body}")
# Paginated messages
messages = client.conversations.get_messages("conv_123", limit=50, before="msg_456")
# Update conversation metadata
client.conversations.update("conv_123", metadata={"tag": "vip"})
Usage
# Current usage summary
usage = client.usage.get()
print(f"Plan: {usage.plan.name}")
print(f"Numbers: {usage.numbers.used}/{usage.numbers.limit}")
print(f"Calls (30d): {usage.stats.calls_last_30d}")
# Daily and monthly breakdowns
daily = client.usage.get_daily(days=7)
monthly = client.usage.get_monthly(months=3)
Webhooks
# Project-level webhook
webhook = client.webhooks.set(url="https://my-server.com/hook", context_limit=10)
client.webhooks.get()
client.webhooks.delete()
# Delivery monitoring
deliveries = client.webhooks.list_deliveries(limit=50)
stats = client.webhooks.get_delivery_stats(hours=24)
print(f"Success rate: {stats.success_rate}%")
Webhook Verification
from agentphone import construct_event, WebhookVerificationError
@app.post("/webhook")
async def handle(request: Request):
body = await request.body()
sig = request.headers["X-Webhook-Signature"]
try:
event = construct_event(body, sig, secret=os.environ["WEBHOOK_SECRET"])
except WebhookVerificationError:
return Response(status_code=403)
if event.event == "agent.message":
print(f"SMS from {event.data.from_number}: {event.data.message}")
Error Handling
The sync and async clients do not automatically retry requests, including message sends and call creation. A timeout, lost connection, or 5xx response can happen after the operation has already succeeded. Do not automatically repeat these writes: doing so can send a duplicate message or place another call. An empty message history is not proof that a send failed.
409 DELIVERY_UNCONFIRMED means a message may already have been sent. Keep
the outcome unresolved and contact support if needed; do not treat it as a
definite failure. Transport exceptions from requests or httpx propagate
to the caller. Check any retry wrappers in your application as well.
The example below handles a read operation. Apply backoff to reads, or retry a write only when the API explicitly confirms it was not accepted.
from agentphone import AgentPhoneError, AuthenticationError, NotFoundError, RateLimitError
try:
call = client.calls.get("bad-id")
except NotFoundError:
print("Call not found")
except RateLimitError:
print("Rate limited — back off and retry")
except AuthenticationError:
print("Invalid API key")
except AgentPhoneError as e:
print(f"API error {e.status}: {e.message}")
Requirements
- Python 3.9+
requests(sync client)httpx(async client, optional)
Links
Metadata
Release files for agentphone 0.19.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| agentphone-0.19.1.tar.gz | 26.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentphone-0.19.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.4 kB
Release files / agentphone-0.19.1.tar.gz
| Download URL | agentphone-0.19.1.tar.gz |
|---|---|
| Size | 26.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
72f7a7ad3fab898842e8a76f89f2dc33bf9210007be1a7b5c1c804b58ed1b926
|
|
BLAKE2b-256 checksum How to use checksums |
de727c40e6e06f0d39d1503a0da9a977cf5a2d003330378cf37f44155bceaa4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / agentphone-0.19.1-py3-none-any.whl
| Download URL | agentphone-0.19.1-py3-none-any.whl |
|---|---|
| Size | 28.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e1c97c2832f5d0b6e8f6396d1579e1b1b1076087fdc923e309cae65a7be86177
|
|
BLAKE2b-256 checksum How to use checksums |
5d9b0f3820b4df8c1b90e8542977fd46c0ac984037242a17de3a396ef0ea08f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Security for agentphone 0.19.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.