Pipeline
What an agent hears, decides and speaks with, read as three stages and what they cost.
The org's own key, holding pipeline. These endpoints belong to the agent: a tenant's own dashboard
opens them as readily as the console does.
GET /v1/agents/{slug}/pipeline#
What one agent would hear, decide and speak with on its next call, and how fast its last calls were.
{ "agent": "clinica-norte",
"hears": { "vendor": "soniox", "model": null, "voice_id": null, "language": "es" },
"decides": { "vendor": "anthropic", "model": "claude-haiku-5-5", "voice_id": null, "language": null },
"speaks": { "vendor": "elevenlabs", "model": null, "voice_id": "a-voice", "language": "es" },
"greeting": { "say": "Clínica Norte, buenas.", "reply": null, "allow_interruptions": null },
"providers": [ … ],
"defaults": { "llm": "anthropic", "stt": "soniox", "tts": "elevenlabs" },
"models": { "tts/elevenlabs": [ "eleven_flash_v2_5" ] },
"calls": 12,
"medians": [ { "name": "transcription_delay", "seconds": 0.13, "turns": 41 } ],
"unavailable_reasons": { "speaks": "elevenlabs has no key: the org brought none and the box holds none" } }
Each stage is the vendor a call would be built with: the agent's settings, else the providers
row's default for that modality. Nothing is built to answer. providers are the rows
GET /v1/providers answers this org (Provider keys); defaults and models
are the providers row's. medians pools every turn of the agent's last twenty calls, livekit's own
field names, the median and not the mean; a measure nobody took has no row. Two rows are the
runtime's: dead_air, the silence between the caller stopping and the agent starting, and
talk_share, the agent's part of each call's talking, whose seconds is a fraction 0 to 1 and
whose turns counts calls (Evals). unavailable_reasons
names a stage with no key to run on, in the refusal's own words, so a screen says so before a line
goes dead. When no app holds the agent the stages come from a bare declaration and the settings.
Changing any of it is the agent's settings (Settings), applied on the next call; this endpoint reads.
The hold melody — GET · PUT /v1/agents/{slug}/pipeline/hold-audio#
What a caller hears while a tool runs or the model has not said its first word: the platform's melody, silence, or a clip the org uploaded. It starts after 2.5 s of the agent being quiet, so a tool or a model that answers inside that plays nothing, and stops at the answer. One choice per agent per environment, whoever holds it.
{ "played": "custom", "name": "espera.mp3", "seconds": 41.2, "sha256": "9f2c…" }
default is the platform's own melody, off is silence, custom an uploaded clip with the name it
arrived under, its length, and the sha256 of the converted bytes, which is what a worker
recognises a clip it already fetched by.
PUT …/hold-audio?name= uploads one, and the body is the file: a wav, an mp3, an ogg, a flac
or an m4a, told apart by its first bytes and nothing else (FFmpeg is named the container and probes
nothing, so a playlist or a script it would otherwise follow is no audio here); no multipart. It
is converted here, once, to Ogg Opus 48 kHz mono, so no worker decodes a stranger's upload
mid-call; 400 in a sentence for a file that is no audio, one longer than five minutes or shorter
than a second, 400 over 20 MB.
GET …/hold-audio/audio is the org's own clip, audio/ogg, to hear before a caller does; 404
while the agent plays no clip of its own. PUT …/hold-audio/played {played: "default" | "off"}
needs no file; an uploaded clip is forgotten either way.
The worker's own reads are GET /v1/agents/{slug}/hold-audio and …/audio, on the fleet's key,
in the call's scope.