Skip to main content

Overview

The Pipecat development runner provides a unified way to run voice AI bots across multiple transport types. It handles infrastructure setup - creating Daily rooms, managing WebRTC connections, and routing telephony calls.

Installation

What is a Runner?

A runner in Pipecat refers to a “bot runner”, an HTTP service that provides a gateway for spawning bots on-demand. It’s the component that enables your bot to run by providing it with server infrastructure and connection details like rooms and tokens. A bot runner typically creates transport sessions (like Daily WebRTC rooms), generates authentication tokens for both bots and users, spawns new bot processes when users request sessions, and manages bot lifecycle and cleanup. Think of it as the bridge between incoming user connections and your bot logic.

How the Development Runner Works

The development runner operates as a FastAPI web server that automatically discovers and executes your bot code. By default it starts a single server that supports all transports simultaneously — WebRTC, Daily, telephony (Twilio, Telnyx, Plivo, Exotel), and plain WebSocket. Clients choose which transport they want per-request by sending a "transport" field in the POST /start body; you no longer restart the server to switch transports.
  • WebRTC connections: It serves a built-in web interface where users can connect directly as well as an endpoint to create new WebRTC sessions
  • Daily integration: It provides endpoints that create new rooms and tokens and redirect users to join them
  • Telephony providers: For Twilio, Telnyx, and Plivo, it sets up webhook endpoints that handle incoming calls and establish WebSocket connections for audio streaming
  • Plain WebSocket: It exposes a /ws-client endpoint for non-telephony WebSocket clients (e.g. browser apps using protobuf framing)
The runner registers the infrastructure for every transport and dispatches each connection to the right handler. It then discovers your bot function and spawns new instances whenever users connect. This means you can focus on writing your bot logic while the runner handles all the server infrastructure, connection management, and transport-specific details.
Passing -t/--transport is optional. Omit it to support all transports at once (the default). Pass it (e.g. -t daily) to restrict the server to a single transport and make it the default for /start.
Your bot code receives runner arguments that contain everything it needs, including Daily room URLs and tokens, WebRTC connections, or WebSocket streams for telephony. The runner abstracts away the complexity of managing these different connection types, providing a unified interface for building bots that work across multiple platforms.

Pipecat Cloud Ready

The bot runner is designed to be cloud-ready, meaning that you can run the same bot code locally and deployed to Pipecat Cloud without any modifications. It automatically handles the differences in transport setup, providing you with the flexibility to test locally using an open source transport, like SmallWebRTCTransport, but run in production using Daily or telephony transports.

Building with the Runner

Now let’s build a practical example to see how this works. The key insight is that your bot code is structured into two parts: the core bot logic that works with any transport, and the entry point that creates the appropriate transport based on the runner arguments. Here’s the basic structure:
The run_bot() function contains your actual bot logic and is transport-agnostic. The bot() function is the entry point that the runner calls - it creates the appropriate transport and passes it to your bot logic. This separation allows the same bot code to work across different transports. When you run this with python bot.py, the development runner starts a web server and opens a browser interface at http://localhost:7860/client. Each time someone connects, the runner calls your bot() function with WebRTC runner arguments.

Supporting Multiple Transports

To make your bot work across different platforms, you can detect the transport type from the runner arguments and create the appropriate transport. Here’s how to support both Daily and WebRTC:
Now you can run your bot. A single server supports every transport at once, and the client picks which one to use when it starts a session:
If you want to limit the server to one transport (and make it the default for /start), pass -t/--transport:

Understanding Runner Arguments

Runner arguments are how the runner communicates transport-specific information to your bot. The runner determines which transport to use based on the command-line arguments, then creates the appropriate runner arguments:
  • DailyRunnerArguments: Contains room_url, token (Optional), body (Optional) for joining Daily rooms
  • SmallWebRTCRunnerArguments: Contains webrtc_connection for local WebRTC sessions, body (Optional) for WhatsApp call metadata
  • WebSocketRunnerArguments: Contains websocket for both telephony and plain WebSocket connections. The transport_type field distinguishes them — "websocket" for plain (non-telephony) clients connecting via /ws-client, or a telephony provider value for connections via /ws.
All runner arguments also include:
  • handle_sigint: Whether the bot should handle SIGINT (Ctrl+C) signals (managed automatically)
  • handle_sigterm: Whether the bot should handle SIGTERM signals (managed automatically)
For WhatsApp connections, SmallWebRTCRunnerArguments.body contains a WhatsAppConnectCall object with caller metadata (phone number, call ID, direction, timestamp). This allows your bot to access caller information without additional API calls.
handle_sigint and handle_sigterm are development only features and cannot be used when deploying to Pipecat Cloud.
The runner handles all the complex setup - creating Daily rooms, generating tokens, establishing WebSocket connections - and provides your bot with everything it needs through these runner arguments. Notice how we use lazy imports (from pipecat.transports.daily.transport import ...) inside the conditional blocks. This ensures that transport-specific dependencies are only required when that transport is actually used, making your bot more portable.
RunnerArguments is the base class for all runner arguments. It provides a common interface for the runner to pass transport-specific information to your bot.

Environment Detection

When building bots that work both locally and in production, you often need to detect the execution environment to enable different features. The development runner sets the ENV environment variable to help with this:

Environment Values

The development runner automatically sets environment variables based on how your bot is running:
  • Local development: ENV=local (set by the development runner)
  • Production/Cloud deployment: ENV is not set or has a different value
This allows you to easily customize behavior between development and production environments:

All Supported Transports

The development runner supports seven transport types, each designed for different use cases. By default all are available from one server; the client selects one per session via the "transport" field in the /start request. The -t <transport> form below is the optional way to restrict the server to a single transport.

WebRTC ("transport": "webrtc")

Local WebRTC connections with a built-in browser interface. Perfect for development and testing. This is the default transport when a /start request omits the "transport" field.
Runner Arguments: SmallWebRTCRunnerArguments
  • webrtc_connection: Pre-configured WebRTC peer connection

Daily ("transport": "daily")

Integration with Daily for production video conferencing with rooms, participant management, and Pipecat client compatibility.
Runner Arguments: DailyRunnerArguments
  • room_url: Daily room URL to join
  • token: Authentication token for the room
  • body: Request data from /start endpoint (dict) or dial-in webhook data
Available Endpoints:
  • GET /daily: Browser redirect that creates a room and joins it
  • POST /start: RTVI-compatible endpoint for programmatic access (send "transport": "daily")
  • POST /daily-dialin-webhook: PSTN dial-in webhook handler (requires --dialin flag)
GET / redirects to the prebuilt client UI at /client/ for all transports. The Daily browser redirect moved to GET /daily in Pipecat 1.3.0.

Telephony ("transport": "twilio" | "telnyx" | "plivo" | "exotel")

Phone call integration through telephony providers. Requires a public webhook endpoint and provider credentials.
The provider-specific XML webhook (POST /) is only registered when you select a single telephony transport with -t (the XML template needs the --proxy hostname). The media WebSocket at /ws is always available. Environment Variables:
  • Twilio: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN
  • Telnyx: TELNYX_API_KEY
  • Plivo: PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN
  • Exotel: None required (no hang-up functionality available)
Environment variables are optional, but when provided will attempt to hang up the call when the session ends. Runner Arguments: WebSocketRunnerArguments
  • websocket: WebSocket connection for audio streaming
The runner automatically detects the telephony provider from incoming WebSocket messages and configures the appropriate serializers and audio settings. Your bot receives a pre-configured FastAPI WebSocket connection ready for telephony audio streaming.

WebSocket ("transport": "websocket")

Plain (non-telephony) WebSocket connections, for clients such as browser apps that speak protobuf framing directly. Selecting this transport on /start returns a wsUrl pointing at the /ws-client endpoint, which the client then connects to.
Runner Arguments: WebSocketRunnerArguments
  • websocket: WebSocket connection for audio streaming
  • transport_type: "websocket" for plain clients (distinguishes them from telephony connections, which arrive on /ws)
Use the transport_type field to branch your bot’s setup — plain WebSocket clients typically use a ProtobufFrameSerializer, whereas telephony clients use a provider-specific serializer.

Command Line Options

The development runner accepts several command-line arguments to customize its behavior:

Key Arguments

--transport / -t: Optional. When omitted, the server supports all transports at once and the client chooses one per /start request. When provided, it restricts the server to a single transport and makes it the default for /start; requests for any other transport are rejected with HTTP 400. Valid values:
  • webrtc: Local WebRTC with browser interface
  • daily: Daily.co integration with room management
  • twilio, telnyx, plivo, exotel: Telephony provider integration
--proxy / -x: Required for most telephony transports (Twilio, Telnyx, Plivo). This should be a publicly accessible hostname (like yourbot.ngrok.io) that can receive webhooks from telephony providers. The runner automatically strips protocol prefixes (http://, https://) if provided. Not required for Exotel, which uses direct WebSocket connections. --direct / -d: Special mode for Daily that bypasses the web server and connects your bot directly to a Daily room. Useful for quick testing but not recommended for production use. --dialin: Enables the /daily-dialin-webhook endpoint for handling Daily PSTN dial-in calls. This endpoint receives webhook data from Daily when a phone call dials into your configured phone number, creates a SIP-enabled room, and spawns your bot. (It no longer requires -t daily, though dial-in is a Daily feature.) --allowed-origins: Restricts which origins can connect to HTTP and WebSocket endpoints. Useful for preventing Cross-Site WebSocket Hijacking (CSWSH) attacks. When set, connections with a missing or disallowed Origin header are rejected. Can be specified multiple times for multiple origins. Defaults to the PIPECAT_ALLOWED_ORIGINS environment variable (comma-separated). Leave unset to allow all origins. --esp32: Enables SDP (Session Description Protocol) modifications needed for ESP32 WebRTC compatibility. Must be used with a specific IP address via --host.

Environment Variables

Different transports require different environment variables: Daily:
  • DAILY_API_KEY: Daily API key for creating rooms and tokens (required for dial-in)
  • DAILY_SAMPLE_ROOM_URL (Optional): Existing room URL to use
  • DAILY_API_URL (Optional): Daily API URL (defaults to https://api.daily.co/v1)
Telephony:
  • TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN: Twilio credentials
  • PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN: Plivo credentials
  • TELNYX_API_KEY: Telnyx API key
Security:
  • PIPECAT_ALLOWED_ORIGINS: Comma-separated list of allowed origins for HTTP and WebSocket connections. When set, the runner and WebSocket transports reject any connection whose Origin header is missing or not in this list. Leave unset or empty to allow all origins. Example: https://example.com,https://app.example.com
The runner automatically uses these environment variables when creating transport sessions and authentication tokens.

Simplifying with the Transport Utility

While the manual approach gives you full control, the create_transport utility provides a much cleaner way to handle multiple transports. Instead of writing conditional logic for each transport type, you define transport configurations upfront and let the utility handle the selection:
The utility automatically:
  • Detects the telephony provider from WebSocket messages
  • Configures the appropriate serializer (Twilio, Telnyx, Plivo, or Exotel)
  • Sets up authentication using environment variables
  • Handles WebRTC, Daily, and plain WebSocket transport creation
Now your bot supports all seven transport types with just two lines of code in the bot() function.

RTVI Integration

The development runner provides RTVI (Real-Time Voice Interface) compatible endpoints for the Pipecat client SDKs. This allows you to use the Pipecat client libraries locally during development as well as when deployed to Pipecat Cloud.

The /start Endpoint

The runner exposes a single unified POST /start endpoint that works for all transports. The client picks the transport via the "transport" field in the request body (default: "webrtc"); the runner then spawns the bot and returns transport-specific connection details in RTVI-compatible format.
If you started the runner with -t <transport>, a /start request for any other transport is rejected with HTTP 400. Request Format ("transport": "webrtc", the default):
Returns a sessionId (and an iceConfig when enableDefaultIceServers is set). The bot starts when the WebRTC offer arrives. Request Format ("transport": "daily"):
createDailyRoom: true (or a configured DAILY_ROOM_URL) makes the runner create a room and token using your DAILY_API_KEY. dailyRoomProperties and dailyMeetingTokenProperties are applied when provided. Response:
Request Format (telephony or plain WebSocket): For "transport": "twilio" | "telnyx" | "plivo" | "exotel" or "transport": "websocket", /start returns the WebSocket URL the client should connect to:
Telephony connections use /ws; plain WebSocket clients use /ws-client (and also receive a sessionId). The bot starts when the connection is established.

The /status Endpoint

GET /status reports which transports the running instance accepts — all of them by default, or the single transport passed via -t:

Accessing Request Data in Your Bot

The body field from the /start request is passed to your bot through DailyRunnerArguments.body:

RTVI Client Example

You can use RTVI client libraries to connect to your local development runner:
The /start endpoint serves every transport. The client SDK’s transport (e.g. DailyTransport, SmallWebRTCTransport) determines the "transport" value sent in the request.

Daily Dial-In Webhook

The development runner can handle Daily PSTN dial-in webhooks when started with the --dialin flag. This allows you to test phone call integrations locally before deploying to production.

Enabling Dial-In Support

How It Works

When a phone call dials into your Daily phone number:
  1. Daily sends a webhook to your configured endpoint with call details
  2. The runner creates a SIP-enabled room with appropriate configuration
  3. Your bot is spawned with dial-in context in runner_args.body
  4. The call is connected to the room where your bot is waiting

Webhook Payload

Daily sends the following data to the /daily-dialin-webhook endpoint:

Accessing Dial-In Data in Your Bot

The dial-in webhook data is passed to your bot through DailyRunnerArguments.body. You need to parse it and configure the Daily transport with dial-in settings:

Configuring Daily Phone Numbers

To test dial-in locally, you need to:
  1. Configure a Daily phone number in your Daily dashboard
  2. Set the webhook URL to your public endpoint (e.g., via ngrok)
  3. Ensure DAILY_API_KEY is set in your environment
Dial-in is a Daily feature and requires a valid DAILY_API_KEY environment variable. --dialin no longer requires -t daily, but you can still pass it to restrict the runner to Daily.

Error Handling and Debugging

The development runner includes built-in error handling to help debug issues with incoming requests and webhook payloads.

422 Validation Error Logging

When FastAPI receives a malformed request that fails Pydantic validation (HTTP 422 Unprocessable Entity), the runner automatically logs both the validation errors and the raw request body. This makes it much easier to debug malformed payloads from any transport — WhatsApp webhooks, WebRTC signaling, telephony providers, or RTVI clients. The runner logs:
  • The request URL path where the error occurred
  • Detailed validation error messages from Pydantic
  • The raw request body (up to 5000 characters)
This automatic logging applies to all runner endpoints and helps identify issues with webhook configurations, client implementations, or API changes without requiring manual debugging code.

Quick Reference

All transports are available from python bot.py; the -t form below restricts the server to one. Access columns assume the default localhost:7860.

Examples

For practical examples of using the development runner with different transports, check out the following:

Runner Examples

Explore the examples for different ways to use the development runner with various transports.