GPT-Live Realtime Voice
Use the GPT-Live WebSocket API for full-duplex voice interaction and session events.
GPT-Live provides full-duplex voice interaction over a persistent WebSocket connection. Within one session, you can send input audio, receive audio and transcripts, update session settings, and maintain multi-turn context. The currently documented model is gpt-live-1; actual availability depends on the current account and model catalog.
wss://api.agtcloud.cn/v1/live/sessionsAuthentication and connection
Server applications use a Bearer API key in the WebSocket handshake header. The model is not included in the query string. Instead, configure it in the first session.start event after connecting.
| Item | Value |
|---|---|
| Connection URL | wss://api.agtcloud.cn/v1/live/sessions |
| Authorization | Bearer your-api-key |
| Model location | session.start.session.model |
| Input audio | Base64-encoded raw PCM audio chunks |
Store long-lived API keys only on trusted servers. This page covers only this WebSocket route; it does not cover WebRTC, SIP, sideband connections, session forking, or recording downloads.
Session flow
- Establish a WebSocket connection with a Bearer API key.
- Send
session.startto configure the model, instructions, audio, and delegation method. - After receiving
session.started, begin sending audio or application commands. - Continuously process input transcripts, output transcripts, output audio, and usage events.
- After stopping audio production, send
session.close. - Continue receiving events and use the final usage in
session.closedto complete session cleanup.
The end of a speaking turn is not the end of the session. Keep the same connection for a multi-turn conversation and send session.close only when the application is ready to end the entire session.
Start parameters
| Field | Type | Description |
|---|---|---|
type | string | Always session.start in the first frame |
event_id | string | Optional client event identifier used to correlate errors |
session.model | string | Required GPT-Live model ID |
session.instructions | string | Optional voice-assistant instructions |
session.audio.format | object | Audio format, such as audio/pcm at 24 kHz |
session.audio.output.voice | string | Output voice, such as marin; available values depend on the model |
session.delegation | object | Delegation method, using client or responses |
After the connection starts, use session.update to change instructions or other mutable session settings, and wait for session.updated to confirm the change. Do not send session.start again on the same connection, and do not use an update event to switch the top-level voice model.
Audio preparation and examples
The examples use headerless, mono, signed 16-bit little-endian PCM audio at 24 kHz. Each 100 ms chunk is 4,800 bytes and is Base64-encoded before being sent.
# Prepare the input audio
ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio.pcm
# Play the saved output audio
ffplay -f s16le -ar 24000 -ac 1 reply.pcmThe Node.js example requires ws. The Python example only checks session startup and shutdown and does not send audio.
npm install ws
python -m pip install websocket-clientimport fs from 'node:fs';
import WebSocket from 'ws';
const key = process.env.API_KEY;
const file = process.argv[2];
if (!key || !file) throw new Error('API_KEY and a PCM file path are required');
const pcm = fs.readFileSync(file);
if (!pcm.length || pcm.length % 2) {
throw new Error('PCM must be non-empty and aligned to 16-bit samples');
}
const ws = new WebSocket('wss://api.agtcloud.cn/v1/live/sessions', {
headers: { Authorization: 'Bearer ' + key },
handshakeTimeout: 10000,
});
const audioOut = fs.createWriteStream('reply.pcm');
let started = false;
let closing = false;
let finalized = false;
let sender;
let drain;
let offset = 0;
let inputFinishedAt = 0;
let lastOutputAt = 0;
function send(event) {
ws.send(JSON.stringify(event));
}
function closeSession(reason) {
if (closing) return;
closing = true;
clearInterval(sender);
console.error('Ending request:', reason);
if (started && ws.readyState === WebSocket.OPEN) {
send({ type: 'session.close' });
drain = setTimeout(() => ws.terminate(), 15000);
} else {
ws.terminate();
}
}
const limit = setTimeout(() => closeSession('60-second example limit'), 60000);
ws.on('open', () => send({
type: 'session.start',
event_id: 'start_001',
session: {
model: 'gpt-live-1',
instructions: 'Respond briefly in English and wait for the user to speak first.',
audio: {
format: { type: 'audio/pcm', rate: 24000 },
output: { voice: 'marin' },
},
delegation: { type: 'client' },
},
}));
ws.on('message', (raw) => {
let event;
try {
event = JSON.parse(raw.toString());
} catch {
closeSession('Invalid JSON');
return;
}
if (event.type !== 'session.output_audio.delta') {
console.log(raw.toString());
}
if (event.type === 'session.started' && !started) {
started = true;
sender = setInterval(() => {
if (closing || ws.readyState !== WebSocket.OPEN) return;
if (offset >= pcm.length) {
inputFinishedAt ||= Date.now();
if (
lastOutputAt &&
Date.now() - Math.max(lastOutputAt, inputFinishedAt) >= 3000
) {
closeSession('Output finished');
return;
}
send({
type: 'session.input_audio.append',
audio: Buffer.alloc(4800).toString('base64'),
});
return;
}
const chunk = pcm.subarray(offset, offset + 4800);
offset += chunk.length;
send({
type: 'session.input_audio.append',
audio: chunk.toString('base64'),
});
}, 100);
}
if (event.type === 'session.output_audio.delta') {
lastOutputAt = Date.now();
audioOut.write(Buffer.from(event.delta, 'base64'));
}
if (event.type === 'session.output_transcript.delta') {
lastOutputAt = Date.now();
}
if (event.type === 'session.closed') {
finalized = true;
ws.close(1000);
}
if (event.type === 'error') closeSession('Service error');
});
ws.on('error', (error) => {
console.error(error.message);
closeSession('Connection error');
});
ws.on('close', (code, reason) => {
clearTimeout(limit);
clearTimeout(drain);
clearInterval(sender);
audioOut.end();
console.error({ code, reason: reason.toString(), finalized });
if (!finalized || code !== 1000) process.exitCode = 1;
});
process.on('SIGINT', () => closeSession('Stopped by user'));Event reference
This API sends Base64 audio with session.input_audio.append. It does not use Muse binary audio frames and endStream, or the OpenAI Realtime input_audio_buffer.* events.
session.start
Starts a GPT-Live session with model, instruction, audio, and delegation settings.
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
type | string | Yes | Always `session.start`. | session.start |
event_id | string | No | Client event identifier. | start_001 |
session.model | string | Yes | GPT-Live model ID. | gpt-live-1 |
session.instructions | string | No | Conversation instructions for the voice assistant. | Respond briefly in English. |
session.audio.format.type | string | No | Audio format type. | audio/pcm |
session.audio.format.rate | integer | No | Audio sample rate. | 24000 |
session.audio.output.voice | string | No | Output voice. | marin |
session.delegation.type | string | Yes | Delegation method. | client |
{
"type": "session.start",
"event_id": "start_001",
"session": {
"model": "gpt-live-1",
"instructions": "Respond briefly in English.",
"audio": {
"format": {
"type": "audio/pcm",
"rate": 24000
},
"output": {
"voice": "marin"
}
},
"delegation": {
"type": "client"
}
}
}Usage and billing
session.usage.updated returns the current cumulative session usage, not the incremental usage for one turn. Do not add values from multiple stages. During a normal shutdown, use session.closed.usage.seconds as the final session duration.
Charges may include voice-session duration, delegated model usage, and tool calls. See the model catalog for current pricing and model availability.
Delegation methods
client: The application handles delegated tasks itself. Use this method when first validating a basic voice session.responses: The server runs the configured Responses model and tools. The target model, tools, and permissions must be available on the current site.
With responses, delegated model and tool usage may incur separate charges. Do not treat MODEL_ID in the example as a model that is always available.
Error handling
| Symptom | What to check |
|---|---|
| Handshake returns 401 | Check the Bearer API key, model permission, and current site address |
| Path returns 404 | Confirm that the final connection path is /v1/live/sessions and that /v1 is not repeated |
| First-frame error or startup timeout | Send session.start immediately after connecting and wait for session.started before sending audio |
| No audio or distorted audio | Check PCM bit depth, byte order, sample rate, channel count, and removal of the WAV header |
No session.closed event | Save the last cumulative usage, error event, and close reason; do not automatically replay the entire audio input |
| Delegated task rejected | Check the Responses model, tools, permissions, and current site capabilities |