Skip to main content

Prerequisites

  • Python 3.11+
  • An Unpod API key (see Step 2)
Local development: the production API at wss://api.unpod.io is not yet live. Until it is, run supervoice locally and point the SDK at it by overriding base_url (the SDK defaults to wss://api.unpod.io):
# Terminal 1 — start the backend
cd supervoice && uv run uvicorn supervoice.main:app --port 8000 --reload
Then pass base_url="ws://127.0.0.1:8000" to AgentRunner(...) and set UNPOD_SERVICE_BASE_URL="http://localhost:8000/platform" for the management client.

Choose Your Path

Phone / Telephony

Users call a phone number. Provision a number, attach it to your Speech Pipe, handle inbound calls.

Browser / App

Users connect from a web or mobile app. Embed the Web SDK in your frontend, no phone number needed.
Both paths share Steps 1-5 (SDK install, API key, voice profile, create Speech Pipe, build a dialog flow). They diverge at Step 6.

Step 1 - Install the SDK

unpod is not yet on PyPI. Install it from source under Python 3.12. anthropic and python-dotenv install normally via pip.
1. Clone and install unpod from source (Python 3.12 required):
git clone <unpod-sdk-repo>
python3.12 -m pip install -e ./unpod-sdk
2. Install remaining packages:
python3.12 -m pip install anthropic python-dotenv

Step 2 - Set Your API Key

task is the go-task runner. Install it once:
go install github.com/go-task/task/v3/cmd/task@latest
Then generate a key:
task create-api-key
Create a .env file in your project root and paste the key:
UNPOD_API_KEY="sk-..."

# Required — Anthropic key for your dialog agent:
ANTHROPIC_API_KEY="sk-ant-..."

# --- Local dev only (remove for production) ---
UNPOD_SERVICE_BASE_URL="http://localhost:8000/platform"        # AsyncClient REST API
UNPOD_ORCHESTRATOR_URL="ws://localhost:8090"                   # AgentRunner WebSocket
UNPOD_ORCHESTRATOR_BASE_URL="http://localhost:8090"            # AsyncClient orchestrator ops (transfer, end)
All scripts in this guide call load_dotenv(override=True) before any from unpod import ... statement. The SDK reads UNPOD_SERVICE_BASE_URL at module import time — loading .env after the import is too late and silently falls back to the production URL.

Step 3 - Pick a Voice Profile

from dotenv import load_dotenv
load_dotenv(override=True)

import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient() as client:
        profiles = await client.voice_profiles.list(language="en")
        for p in profiles:
            print(p.id, p.name, p.gender, p.quality)

asyncio.run(main())
Example output (profile IDs vary by account and region):
vp_riya    Riya   female  high
vp_james   James  male    standard
Seed profiles (shown above) are available immediately. Custom voices must be created in the Unpod dashboard before they appear in this list. Pass p.id (the profile ID, e.g. vp_riya) as voice_profile in Step 4.

Step 4 - Create a Speech Pipe

A Speech Pipe ties a voice profile to your agent. The agent_id is a string you choose — it links the pipe to the AgentRunner you run in Step 6T.
from dotenv import load_dotenv
load_dotenv(override=True)

import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient() as client:
        pipe = await client.pipes.create(
            name="Support Bot",
            voice_profile="vp_riya",         # profile_id from Step 3 (p.id)
            agent_id="my-support-agent",     # your identifier — must match AgentRunner
            recording=True,
            max_call_duration_s=600,
        )
        print("Pipe ID:", pipe.pipe_id)

asyncio.run(main())
Save the pipe.pipe_id — you will use it in the steps below. A pipe without agent_id will show as degraded and calls will not be dispatched to your runner.

Step 5 - Build a Dialog Flow

Both paths use the same conversation logic. Create a flow once and save it to a file your entrypoint will load:
import asyncio
from superdialog import create_dialog_flow

async def main():
    flow = await create_dialog_flow(
        prompt=(
            "You are Alex, a friendly support agent. "
            "Greet the user, ask how you can help, resolve their issue, "
            "and end the conversation when done."
        ),
        llm="anthropic/claude-haiku-4-5",
    )
    flow.save("support.json")

asyncio.run(main())
This writes support.json, which the entrypoint loads with Flow.load("support.json") in the next step.

Telephony Path

Step 6T - Attach a Phone Number

from dotenv import load_dotenv
load_dotenv(override=True)

import asyncio
from unpod import AsyncClient

PIPE_ID = "pipe_..."
NUMBER_ID = "num_..."   # from client.numbers.list()

async def main():
    async with AsyncClient() as client:
        number = await client.numbers.attach(NUMBER_ID, PIPE_ID)
        print("Attached:", number.number, "->", number.pipe_id)

asyncio.run(main())
See Phone Numbers for how to list, provision, or bring your own number.

Step 7T - Write Your Entrypoint

agent_id is a string you define (e.g. "my-support-agent"). It must be identical in both pipes.create() (Step 4) and AgentRunner below — this is how Unpod routes inbound calls to your runner.
Create agent.py:
from dotenv import load_dotenv
load_dotenv(override=True)

import os
from anthropic import AsyncAnthropic
from unpod import AgentRunner, CallContext
from unpod.adapters import AnthropicAdapter

async def handle_call(ctx: CallContext) -> None:
    ctx.session.dialog_machine = AnthropicAdapter(
        client=AsyncAnthropic(),
        model="claude-haiku-4-5-20251001",
        system_prompt=(
            "You are Alex, a friendly support agent. "
            "Greet the user, ask how you can help, resolve their issue, "
            "and end the conversation when done."
        ),
    )
    await ctx.session.run()

AgentRunner(
    entrypoint=handle_call,
    agent_id="my-support-agent",    # must match agent_id in pipes.create()
    api_key=os.getenv("UNPOD_API_KEY"),
    max_sessions=10,
).start()

Step 8T - Run and Call

python3.12 agent.py
Call the number you attached. The runner connects to the Unpod orchestrator and dispatches inbound calls to your entrypoint. The process stays running and accepts calls until you stop it.

Web / App Path

Step 6W - Create a Session Token

Your backend generates a short-lived session token for each user. The browser uses this token to connect.
from dotenv import load_dotenv
load_dotenv(override=True)

import asyncio
from unpod import AsyncClient

async def create_session(pipe_id: str, user_id: str) -> str:
    async with AsyncClient() as client:
        token = await client.sessions.create_token(
            pipe_id=pipe_id,
            metadata={"user_id": user_id},
        )
        return token.token

asyncio.run(create_session("pipe_...", "usr_123"))
Return this token to your frontend. Tokens are single-use — check token.expires_at for the actual expiry time.

Step 7W - Embed the Web SDK

Install the browser SDK:
npm install @unpod/web-sdk
Connect from your frontend:
import { UnpodSession } from "@unpod/web-sdk";

const session = new UnpodSession({
  token: "<token-from-your-backend>",
});

await session.connect();   // starts microphone + speaker
session.on("agent_reply", (text) => console.log("Agent:", text));
session.on("user_turn", (text) => console.log("User:", text));

// End the session
await session.disconnect();
The same AgentRunner entrypoint from Step 7T handles both phone and browser sessions - no changes needed.

Step 8W - Run Your Agent

python3.12 agent.py
The runner accepts sessions from both telephony and web SDK connections on the same agent_id.

What’s Next

Session Controls

Transfer, pause recording, inject filler phrases.

Hooks & Events

React to turns, interruptions, and call lifecycle.

Outbound Calls

Programmatically initiate calls from your backend.

SuperDialog Integration

Richer flows with tools, branching, and memory.