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 backendcd 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.
go install github.com/go-task/task/v3/cmd/task@latest
Then generate a key:
task create-api-key
Go to app.unpod.dev → Settings → API Keys and create a new 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 APIUNPOD_ORCHESTRATOR_URL="ws://localhost:8090" # AgentRunner WebSocketUNPOD_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.
from dotenv import load_dotenvload_dotenv(override=True)import asynciofrom unpod import AsyncClientasync 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 highvp_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.
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_dotenvload_dotenv(override=True)import asynciofrom unpod import AsyncClientasync 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.
Both paths use the same conversation logic. Create a flow once and save it to a
file your entrypoint will load:
import asynciofrom superdialog import create_dialog_flowasync 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.
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_dotenvload_dotenv(override=True)import osfrom anthropic import AsyncAnthropicfrom unpod import AgentRunner, CallContextfrom unpod.adapters import AnthropicAdapterasync 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()
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.