Skip to main content

What Is a Speech Pipe?

A Speech Pipe in Unpod ties together a voice profile (STT + TTS), telephony (phone numbers), and a pointer to your agent brain into a single deployable unit. When a call arrives on a number attached to the pipe, the orchestrator dispatches it to your AgentRunner process.
Animated Speech Pipe flow showing a phone number routing into a Speech Pipe, the voice profile connecting to the STT/TTS stack, and dispatch to AgentRunner and CallContext.

Creating a Pipe

import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient(api_key="sk-...") as client:
        pipe = await client.pipes.create(
            name="Support Bot",
            voice_profile="vp_en_female_hd",    # from voice_profiles.list()
            agent_id="my-bot",                   # links to your AgentRunner
            recording=True,                      # store call recordings
            max_call_duration_s=600,             # 10-minute hard cap
        )
        print("Created:", pipe.pipe_id, pipe.name)

asyncio.run(main())

create() parameters

ParameterTypeDefaultDescription
namestrrequiredDisplay name for the pipe
voice_profilestr | NoneNoneVoice profile ID (vp_...)
agent_idstr | NoneNoneID of your AgentRunner brain — links the pipe to your runner worker
agent_endpointstr | NoneNoneHTTP endpoint for the dialog brain (HTTPAdapter)
recordingboolFalseEnable call recording
max_call_duration_sint3600Hard cap in seconds (default 1 hour)

Listing Pipes

import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient(api_key="sk-...") as client:
        pipes = await client.pipes.list()
        for p in pipes:
            print(p.pipe_id, p.name, p.voice_profile_id or "no voice profile")

asyncio.run(main())

Pipe fields

FieldTypeDescription
pipe_idstrSpeech Pipe ID (pipe_...)
namestrDisplay name
voice_profile_idstr | NoneVoice profile ID
agent_idstr | NoneYour AgentRunner brain ID
agent_endpointstr | NoneHTTP dialog endpoint
recordingboolRecording enabled
max_call_duration_sintCall duration hard cap
createddatetimeCreation timestamp
modifieddatetimeLast modified timestamp

Getting a Single Pipe

import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient(api_key="sk-...") as client:
        pipe = await client.pipes.get("pipe_...")
        print(pipe.name, pipe.voice_profile_id, pipe.recording)

asyncio.run(main())

Updating a Pipe

import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient(api_key="sk-...") as client:
        # Update any subset of fields
        pipe = await client.pipes.update(
            "pipe_...",
            name="Premium Support Bot",
            voice_profile="vp_en_female_hd",
            recording=True,
            max_call_duration_s=1200,
        )
        print("Updated:", pipe.pipe_id, pipe.name)

asyncio.run(main())

Deleting a Pipe

Deleting a pipe does not automatically detach phone numbers. Detach all numbers before deleting to avoid orphaned routing.
import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient(api_key="sk-...") as client:
        # Detach numbers first
        numbers = await client.numbers.list()
        for n in numbers:
            if n.pipe_id == "pipe_...":
                await client.numbers.detach(n.id)

        # Then delete
        await client.pipes.delete("pipe_...")
        print("Deleted")

asyncio.run(main())

Using a Pipe with an HTTP Dialog Endpoint

If you already have a chatbot or API (in any language) and want to make it voice-enabled, point the pipe at an HTTP endpoint. Unpod calls your endpoint on every user turn, you return the reply.
import asyncio
from unpod import AsyncClient

async def main():
    async with AsyncClient(api_key="sk-...") as client:
        pipe = await client.pipes.create(
            name="Webhook Bot",
            voice_profile="vp_en_male_std",
            agent_endpoint="https://your-api.example.com/dialog/turn",
        )
        print("Pipe with HTTP endpoint:", pipe.pipe_id)

asyncio.run(main())

HTTP Protocol

Unpod sends a POST request on every user turn:
POST https://your-api.example.com/dialog/turn
Content-Type: application/json

{
  "text": "I'd like to reschedule my appointment",
  "context": {
    "call_id": "call_abc123",
    "session_id": "sess_xyz789",
    "turn_number": 3,
    "direction": "inbound",
    "user_number": "+919876543210",
    "data": {}
  }
}
Your endpoint must respond with:
{
  "text": "Sure, I can help with that. What date works for you?"
}
This protocol is not OpenAI-compatible. It is a simple text-in / text-out interface — your endpoint owns all LLM or chatbot logic. You can forward to OpenAI, Gemini, a rules engine, or anything else under the hood.
Alternatively, use the HTTPAdapter inside an AgentRunner entrypoint to call a remote endpoint from within the SDK:
from unpod import AgentRunner, CallContext
from unpod.adapters.http import HTTPAdapter

async def handle_call(ctx: CallContext) -> None:
    ctx.session.dialog_machine = HTTPAdapter(
        url="https://your-api.example.com/dialog/turn",
    )
    await ctx.session.run()

AgentRunner(entrypoint=handle_call, agent_id="my-bot").start()

The recommended pattern for production is a dedicated AgentRunner process:
from unpod import AgentRunner, CallContext

AGENT_ID = "my-bot"   # must match the pipe's agent_id

async def handle_call(ctx: CallContext) -> None:
    await ctx.session.say("Hello, how can I help you?")
    await ctx.session.run()

runner = AgentRunner(
    entrypoint=handle_call,
    agent_id=AGENT_ID,
    max_sessions=20,
)
runner.start()
The agent_id in AgentRunner must match the pipe’s agent_id in the platform. The orchestrator uses it to route dispatches to the correct runner worker.

Next Steps

SDK Setup

Install and configure the AgentRunner for production.

Session Controls

Say, transfer, end, and record during live calls.