> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.agentphone.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.agentphone.ai/_mcp/server.

# Python SDK

> Official Python SDK for the AgentPhone API

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

[![PyPI](https://img.shields.io/pypi/v/agentphone)](https://pypi.org/project/agentphone/)

## Installation

```sh
pip install agentphone
```

For async support:

```sh
pip install agentphone[async]
```

## Quick start

```python
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

```python
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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```python
# 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)

```python
# 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

```python
# 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

```python
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`.

```python
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

```python
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

```python
with AgentPhone(api_key="YOUR_API_KEY") as client:
    agents = client.agents.list()
# session is automatically closed
```

### Custom base URL and timeout

```python
client = AgentPhone(
    api_key="YOUR_API_KEY",
    base_url="https://api.agentphone.ai",
    timeout=30.0,
)
```

## Links

* [PyPI package](https://pypi.org/project/agentphone/)
* [GitHub organization](https://github.com/AgentPhone-AI)