> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unpod.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Make Your First Phone Call

> Take the agent you built in the browser quickstart to a real inbound or outbound phone call.

You built and talked to your agent in the browser in the
[Quickstart](/get-started/quickstart). Now give it a phone number.

Nothing about the agent changes - same `entrypoint`, same
[`LLMAgent`](/get-started/core-concepts#agent-brain) brain, same
[`AgentRunner`](/get-started/core-concepts#agentrunner) against Unpod's hosted
speech service. The only new work is provisioning: telling Unpod which phone
number routes to your agent.

## What you'll add

* **A [Speech Pipe](/get-started/core-concepts#pipe)** - the configuration entity
  that binds a voice profile, recording settings, and the `agent_id` that points
  at your runner.
* **A [phone number](/get-started/core-concepts#number)** - synced from a
  [trunk](/get-started/core-concepts#trunk) and attached to that pipe.

You reference a [voice profile](/get-started/core-concepts#voice-profile) from
the read-only catalog when you create the pipe; you do not create one.

## Provision the number

Provisioning uses the **Management API** - the REST half of the [SDK](https://github.com/unpod-ai/unpod-python-sdk), reached
through `AsyncClient`. It reads `UNPOD_API_KEY` and derives its REST endpoint
from `UNPOD_BASE_URL` (`https://<host>/platform`) - both set in the
[Quickstart](/get-started/quickstart#set-your-environment).

If you need one-off overrides in code, pass `base_url=` to `AsyncClient` or
`AgentRunner`; those arguments win over `.env` for that process only.

Run this once to pick a voice profile, create the pipe, sync numbers from your
trunks, and attach the first available number:

```python theme={null}
# setup.py - run once to provision your phone number
import asyncio
from unpod import AsyncClient

async def setup() -> None:
    async with AsyncClient() as client:
        # 1. Pick a voice profile from the read-only catalog.
        profiles = await client.voice_profiles.list(language="en")
        if not profiles:
            print("No voice profiles found.")
            return
        vp = profiles[0]
        print(f"Using voice profile: {vp.name} ({vp.profile_id})")

        # 2. Create the Speech Pipe. agent_id MUST match your AgentRunner.
        pipe = await client.pipes.create(
            name="browser-agent",
            voice_profile=vp.name,        # name (case-insensitive) or profile_id
            agent_id="browser-playground",  # must match AgentRunner's agent_id
            recording=True,
            max_call_duration_s=600,
        )
        print(f"Created Speech Pipe: {pipe.pipe_id}")

        # 3. Sync numbers from your trunks.
        summary = await client.numbers.sync()
        print(f"Sync result: {summary}")  # {"synced": int, "new": int}

        # 4. Attach the first available number to the pipe.
        available = await client.numbers.list(status="available")
        if not available:
            print("No available numbers - register a trunk first.")
            return
        number = await client.numbers.attach(available[0].number_id, pipe.pipe_id)
        print(f"Attached {number.number} -> Speech Pipe {pipe.pipe_id}")

asyncio.run(setup())
```

<Note>
  The `agent_id` on the pipe (`"browser-playground"`) is the same string your
  `AgentRunner` registers under in the Quickstart. They must match exactly, or
  inbound calls never reach your runner. See
  [IDs You'll Meet](/get-started/core-concepts#ids-youll-meet).
</Note>

### You need a trunk first

`numbers.sync()` pulls numbers from your [trunks](/get-started/core-concepts#trunk) -
the SIP connections that carry calls between a telephony provider and Unpod. If
`numbers.list(status="available")` comes back empty, you have no trunk yet.

Register one with `client.trunks.create(...)` before running the setup above.
You configure a trunk once; after that you work with numbers, not SIP. See
[Trunks](/speech-stack/trunks) for both trunk types, or the
[Setup Checklist](/speech-stack/setup-checklist) for the full production path.

## Answer an inbound call

This is the agent from the Quickstart, unchanged. The same `entrypoint` and the
same `AgentRunner` registered against `wss://<UNPOD_BASE_URL>`, using your real
`UNPOD_API_KEY`:

```python theme={null}
# agent.py - standalone runner, no playground server needed
import asyncio
import os
from unpod import AgentRunner, CallContext
from superdialog import LLMAgent

async def entrypoint(ctx: CallContext) -> None:
    ctx.session.dialog_machine = LLMAgent(
        llm="anthropic/claude-haiku-4-5-20251001",
        system_prompt="You are a helpful voice assistant. Keep answers under 3 sentences.",
    )
    await ctx.session.run()

def build_runner() -> AgentRunner:
    return AgentRunner(
        entrypoint=entrypoint,
        agent_id=os.getenv("AGENT_ID", "browser-playground"),
        # base_url and api_key derive from UNPOD_BASE_URL / UNPOD_API_KEY
        # unless you pass explicit args here.
    )

async def run_agent() -> None:
    await build_runner().run()

if __name__ == "__main__":
    asyncio.run(run_agent())
```

Start the runner:

```bash theme={null}
python agent.py
```

The runner connects to the orchestrator and waits. Now call the number you
attached. Unpod recognises the number, looks up its pipe, sees the pipe's
`agent_id`, and dispatches the call to your waiting runner. Your agent answers
and speaks - the same brain you heard in the browser, now on the phone.

<Note>
  The runner does not need to restart when you provision the number. Run `setup.py`
  once, then leave the runner up; it serves every inbound call until you stop it.
</Note>

## Make an outbound call (optional)

Inbound is one direction. To have your agent place a call, use `calls.create`
with the pipe and the destination number:

```python theme={null}
# call_out.py - dispatch an outbound call
import asyncio
from unpod import AsyncClient

PIPE_ID = "pipe_..."   # from setup.py
TO_NUMBER = "+19995550001"

async def make_call() -> None:
    async with AsyncClient() as client:
        call = await client.calls.create(
            pipe_id=PIPE_ID,
            to_number=TO_NUMBER,
        )
        print(f"Outbound call {call.call_id} -> {TO_NUMBER} (status: {call.status})")

asyncio.run(make_call())
```

`calls.create` enqueues the call and returns immediately with `status="pending"`.
Unpod dials out, then dispatches the answered call to the same running
`AgentRunner` - your `entrypoint` handles outbound exactly as it handles inbound.

<Note>
  You can dispatch by agent instead of by pipe: pass `agent_id=` to
  `calls.create` and omit `pipe_id`. Unpod resolves a pipe bound to that
  `agent_id` server-side (`agent_id` wins if you pass both). `to_number` is always
  required.
</Note>

## Next steps

<CardGroup cols={3}>
  <Card title="Production setup" icon="list-checks" href="/speech-stack/setup-checklist">
    The full path: trunks, numbers, recording, and deployment.
  </Card>

  <Card title="Outbound calls" icon="phone-outgoing" href="/speech-stack/outbound-calls">
    Campaigns, dynamic instructions, and per-call data.
  </Card>

  <Card title="Use your own agent" icon="bot" href="/speech-stack/bring-your-agent">
    Plug in LangChain, an HTTP endpoint, or any brain you already have.
  </Card>
</CardGroup>
