---
title: "Open an Omni voice-agent session (WebSocket)"
method: GET
path: "/v1/omni"
tags: ["Omni"]
---

# Open an Omni voice-agent session (WebSocket)

`GET /v1/omni`

## Request

- Base URL: `https://api.pyai.com`
- URL: `https://api.pyai.com/v1/omni`
- Auth: one of:
  - HTTP bearer
  - API key in header `x-api-key`

**This is the primary way to build an AI voice agent on PyAI.** Open this WebSocket, send one `configure` frame (voice, persona, knowledge endpoint, tools), then stream PCM16 audio both ways. There is nothing to create first: the session is authorized by your key's org, and the whole agent travels in the `configure` frame.

Connect with `wss://api.pyai.com/v1/omni?format=pcm16&rate=24000` (`rate=16000` or `rate=8000` for telephony; use `rate=8000` for an 8 kHz G.711/Twilio leg so the only conversion is μ-law companding, no resampling, see the Telephony audio reference). Every binary frame starts with a one-byte kind: send audio as `0x01 || pcm16_bytes` and control JSON as `0x03 || utf8_json`. Server audio uses the same `0x01` prefix. Untagged PCM is not audio and is ignored.

Requires the `omni:session` scope (or the `omni:*` wildcard). The optional `session_label` is an opaque tag echoed to your own knowledge endpoint so you can branch per session; any value in your org's namespace is accepted (PyAI stores no per-agent state).

**Auth:** send the key as the subprotocol, after the `pyai.v1` marker: `Sec-WebSocket-Protocol: pyai.v1, pyai-key.<API_KEY>` (browsers can't set headers on a WebSocket; server clients may use `?api_key=` instead). PyAI echoes only the marker, so your key is never reflected in the 101. **Browser apps:** if the client can't hold your secret key, mint a short-lived origin-locked token with `POST /v1/omni/sessions` first and use that token as the subprotocol.

See the Omni wire protocol reference for the frame catalog and close codes.
The configured acknowledgement reports voice_instruct_supported for the selected synthesis path. Current Omni routes support neutral delivery only and report false. Unsupported custom voice_instruct values return unsupported_voice_instruct before applying configure. Omit the field to retain the session value, or send an empty string to reset a saved custom instruction to neutral. A missing capability field on an older engine means unknown. A voice_capabilities event reports capability changes after synthesis fallback.

Function calling transports: hosted catalog tools (enable by name), server tools registered with `POST /v1/tools` and bound to the agent, or client-loop tools on this socket (`tool_call` / `tool_result`). An inline `endpoint` or `webhook_url` on `configure.tools[]` is rejected with `{event:"error", code:"unsupported_tool_transport"}` and the configure is not applied.

## Query parameters

- `session_label` string
- `format` 'pcm16'
- `rate` 8000 | 16000 | 24000
- `api_key` string, password

## Other responses

- `101`: Switching Protocols, the Omni WebSocket is open.
- `401`: Missing or invalid API key (`code: unauthorized`)
- `403`: Key lacks the required scope or the origin is not allow-listed (`code: forbidden | origin_not_allowed`)
- `429`: Too many requests or too many concurrent realtime sessions; see Retry-After header (`code: rate_limit_exceeded | concurrency_limit_exceeded | daily_cap_exceeded`)

---

[API](https://skmtc.dev/pyai/apis/pyai-api.md) · [All operations](https://skmtc.dev/pyai/apis/pyai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/pyai/apis/pyai-api/revisions/819d8e7c4d09?raw)
