curl --request GET \
--url https://api.pyai.com/v1/omni \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.pyai.com/v1/omni"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.pyai.com/v1/omni', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pyai.com/v1/omni",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pyai.com/v1/omni"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pyai.com/v1/omni")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pyai.com/v1/omni")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}Open an Omni voice-agent session (WebSocket)
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. Require true when delivery direction is needed; missing means unknown. English Natural supports delivery direction; Standard voices do not. A voice_capabilities event reports loss of support after synthesis fallback. Support does not guarantee subjective acoustic style.
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.
curl --request GET \
--url https://api.pyai.com/v1/omni \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.pyai.com/v1/omni"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.pyai.com/v1/omni', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pyai.com/v1/omni",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pyai.com/v1/omni"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pyai.com/v1/omni")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pyai.com/v1/omni")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}Authorizations
Use Authorization: Bearer pyai_live_... (or pyai_test_...).
Query Parameters
Optional opaque tag for this session, echoed to your own kb_endpoint so you can branch per session. The session is authorized by your key's org (PyAI stores no per-agent state); any value in your org's namespace is accepted. Must be safe as a header value (no control chars, ≤256 chars). Omit it entirely if you don't need per-session correlation.
Audio sample format for both directions.
pcm16 Caller-input sample rate in Hz. Use 24000 for browser/WebRTC, 16000 for wideband input, or 8000 for telephony. Agent output is 24 kHz for both 24 kHz and 16 kHz input sessions; 8 kHz sessions receive 8 kHz output. Read hello.audio_out, which is authoritative.
8000, 16000, 24000 Intentional server-side WebSocket auth option. Prefer the pyai.v1, pyai-key.<API_KEY> subprotocol pair in browsers so the key is not placed in a URL.
512Response
Switching Protocols, the Omni WebSocket is open.