- Endpoint:
wss://api.pyai.com/v1/omni - Scope:
omni:session(or theomni:*wildcard) - Status: GA
0x03 advisory with exactly
{"event":"transcript","role":"assistant","text":"…","final":true}.
This describes the text submitted for speech synthesis; it does not confirm
that the complete reply was played. Caller transcripts still use 0x02.
For voice-quality evaluation, compare this advisory with a transcription of
the captured audio.
A machine-readable AsyncAPI 3.0 definition of this protocol ships alongside
the OpenAPI contract at contracts/omni-asyncapi.yaml.
Field stability. Connect params, auth, the
configure frame, the
kb_endpoint callback, close codes, and metering are stable. Server→client
lifecycle event payloads below document the confirmed envelope; individual
fields marked provisional may gain keys. Branch only on event and ignore
unknown fields. The official SDKs track these for you.1. Connect
Omni is zero-state by default, there is nothing to create first. The session
is authorized by your key’s organization; the agent’s behavior can travel
in the first
configure frame below. A managed Agent profile is an optional
convenience.
2. Auth
Browsers can’t setAuthorization on a WebSocket upgrade, so pass the key as a
subprotocol (browser-safe):
?api_key=pyai_live_... to the URL. The
key is validated at the edge and swapped for the internal engine credential, the
customer key never reaches the engine. Don’t put the key in any other query param.
3. Audio frames
Send microphone audio as binary WebSocket messages in the negotiatedformat/rate (PCM16 little-endian), each prefixed with the 0x01 type
tag. Receive the agent’s speech the same way, 0x01-prefixed binary frames you
strip and play out as they arrive at the sample rate declared by
hello.audio_out. Send caller frames continuously; the engine handles turn
detection and barge-in server-side.
For browser clients with a turn-0 consent_line or greeting, “continuously”
means keeping the audio clock alive with digital-silence frames while that
protected opening plays. Do not uplink microphone or speaker/self-audio until
the playback graph is ready and the opening queue has drained. Then restore
real mic samples; on later replies, preserve normal barge-in after a short
echo-cancellation warm-up. Browser AEC alone is not an opening-audio gate.
Turn finalization: no commit frame
Omni has no clientcommit, EOF, done, or end-of-turn frame. Server-side
turn detection advances while caller-audio frames arrive. Keep sending real-time
0x01 frames, including PCM16 zeroes while the caller is quiet, until reply
audio or the next turn boundary.
Digital zeroes are valid silence. A fixed trailing burst is not a commit signal:
if a late transcript delta lands near the end of that burst, stopping all frames
can leave no later tick on which to close the turn. File/WAV probes should send
20 ms zero frames until reply audio (with a bounded timeout), not send 800 ms
once and then go idle.
{"type":"session_ending"} and the WebSocket close end the session; they do
not finalize a caller turn or request a reply. {"type":"commit"} is a Hear
streaming-STT control, not an Omni control.
Transcript body
Every live0x02 frame contains a non-empty UTF-8 text delta for the current
caller turn. It is not JSON:
{event:"transcript", role:"user", text, final:false, mode:"delta"} and retain
bounded direct-JSON support only for older bridges.
4. Configure frame
Omni accepts complete behavior per session. Immediately after the upgrade, the client sends one JSONconfigure control frame carrying the agent’s
behavior for this call. Control frames are keyed on type and carried as
0x03 || utf8_json, not as bare WebSocket text.
Fields: live vs roadmap
configured.voice_instruct_supported reports whether the selected synthesis
path accepts delivery direction. In the current engine, English Natural
supports it; Standard voices, including Elise and Gwen, do not. A saved or
accepted voice_instruct value alone does not establish support. A missing
capability field on an older engine means unknown, not supported. If delivery
direction is required, require this field to be true before starting the call.
Support means the direction can be passed to synthesis, not that the resulting
audio is guaranteed to match a subjective style.
If English Natural synthesis falls back to Standard during a call, Omni sends
{ "event": "voice_capabilities", "voice_tier": "standard", "voice_instruct_supported": false, "reason": "synthesis_fallback" }.
Use this event to update the capability state from configured.
Voice ids ending in
_en_in are Indian-English voices, not Hindi voices. They
remain compatible with an English-active session; they do not make that session
Hindi. Hindi uses the Standard tier; see the
pricing page for current tier treatment.Agent profiles (POST /v1/agents)
Store persona, role, voice delivery, greeting message, recording disclosure, and tools once;
connect with session_label={agent_id} so the engine loads them from your
stored agent profile (no need to repeat greeting in configure unless overriding).
consent_line → greeting (turn 0) → conversation.
Full walkthrough: Agent greeting messages · REST: POST /v1/agents.
4a. Function calling (tools[])
Omni supports function calling on the live engine. Declare tools in the
configure frame:
{ "event": "tool_confirmation_required", "name": "…", "reason": "…" }
instead of running the tool. After the caller confirms, the next turn may emit
tool_call (client-loop) or invoke a registered server/hosted tool.
Run a client-loop function in your app and reply on the same WebSocket:
{ "type": "tool_result", "call_id": "…", "error": "…" }.
Timeouts: tools are load-bearing (unlike kb_endpoint grounding).
Default per-tool budget is ~5 s (up to ~15 s). Long-running calls may trigger
a brief spoken filler while the engine waits. Results over ~6 KB are truncated.
Server tools: register a webhook with POST /v1/tools and
bind it to the agent (PUT /v1/agents/{id}/tools). Do not put endpoint or
webhook_url on the configure frame, that is rejected with
{ "event": "error", "code": "unsupported_tool_transport" } and the configure
is not applied. Hosted catalog tools are enabled by name. Full guide:
Omni function calling.
4.1 Call-control frames
Call-control tools run inengine mode on phone calls. Omni decides when a
tool should fire and emits a 0x03 control frame. The transport carrying the
phone leg must perform the carrier action.
Every frame is JSON keyed on event. call_id identifies the engine tool
invocation; tool arguments are spread alongside it:
5. kb_endpoint grounding callback
If you set kb_endpoint, the engine calls your endpoint once per user turn to
fetch grounding facts. This call comes from PyAI’s engine, not the browser.
Request (engine → your endpoint):
context string and/or structured passages both work; keep it
small and fast.
Budget: the call has a hard ~300 ms timeout and is fail-open, on timeout
or any error the engine proceeds with empty facts and never blocks the
turn. Treat it as best-effort augmentation; keep it well under budget.
There is no documented retry of the callback, design it as a single
best-effort call.
session_label is how you route per-call context (e.g. look up
the caller’s chart/profile by the tag you connected with).6. Session lifecycle events (server → client)
On connect you receivehello followed by session_started, then turn and flush
events interleaved with audio and plain-text 0x02 transcript deltas, ending in
session_end. Every server JSON body is keyed only on event; the transcript
body is not JSON.
A
flush with reason: "turn_merge" can include cancelled_turn, identifying a
revoked generation when the caller resumes. Clear playback as usual. Preserve
all turn_begin, audio and transcript events: a generation start is not proof
of a spoken reply, and the cancellation receipt does not erase any received output.
7. Control frames (client → server)
8. Close codes
The server uses standard WebSocket close codes plus PyAI-specific application codes. Treat4xxx-class application closes as non-retryable (fix the
request); treat 1011-class closes as retryable with backoff.
A malformed
session_label is rejected before the upgrade as
400 invalid_session_label (an HTTP error, not a WS close).
9. Reconnect & retry
Recommended pattern:- Retry on
1011and4429with exponential backoff; do not retry4401/4403(fix the key/scope first). - Keep per-call state (the
session_label, persona/context, a short running summary) in your backend so a reconnect can re-primeconfigure/kb_endpointand continue gracefully.
10. Metering
Omni sessions reportomni.minutes from session wall-clock duration. Check
the pricing page for the current Omni and managed
telephony rates, included usage, and billing rules.
Realtime WebSocket sessions do not carry an x-pyai-units response header
(that’s HTTP-only), reconcile realtime usage from your call records and usage
data.
See also
Authentication
Key handling and the WS subprotocol.
Errors & limits
Rate, concurrency, and the error catalog.
Telephony audio (8 kHz)
μ-law ↔ PCM16 at 8 kHz for phone legs.
Language support
What’s GA vs roadmap per language.