Repository Public
1# Managing GPT-Live sessions23> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.45After [connecting to GPT-Live](https://developers.openai.com/api/docs/guides/live), use session events to add context, display transcripts, and manage the connection. GPT-Live can listen and speak at the same time. Track transcript text, played audio, and backend task progress separately so your interface can show what the assistant is saying and what work is still running.67This guide assumes your connection has emitted `session.started`. See [Connections](https://developers.openai.com/api/docs/guides/voice-webrtc?api=live) for connection setup and audio streaming, and [Delegation and tools](https://developers.openai.com/api/docs/guides/live-delegation) for backend work.8910111213## Configure a session1415Choose the model, voice, and delegation mode when you create the session. Give the model instructions for the conversation and include relevant history. GPT-Live manages context automatically as the conversation grows.1617### Configuration fields1819| Setting | Configure at startup | Change during the session |20| ------------ | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |21| Model | Set the required `model`. | Start a new session to change it. |22| Instructions | Set `instructions` for conversation behavior, up to 16,384 tokens. | Add instructions with `session.instructions.append`. |23| History | Set `input` to relevant prior text messages. It defaults to `[]`. | Add context with append events. |24| Voice | Set `audio.output.voice` to a supported voice or authorized custom voice. The default is `marin`. | Start a new session to change it. |25| Delegation | Set `delegation.type` to `client` or `responses`. Omitted or `null` delegation selects client mode. | Update Responses settings within the existing mode. |26| Storage | Set `store` to `true` to make the session available for forking. It defaults to `false`. | Choose at startup. |2728### Voice options2930Choose a voice when you create the session. Set `audio.output.voice` to the API name, such as `"quartz"`. GPT-Live includes these additional voice options:3132| Voice | API name | Language | Regional influence | Presentation | Source |33| -------- | ---------- | ---------- | ------------------ | ------------ | --------- |34| Quartz | `quartz` | English | Australian | Feminine | Generated |35| Ripple | `ripple` | English | Australian | Masculine | Natural |36| Vesper | `vesper` | English | British | Masculine | Natural |37| Willow | `willow` | English | Irish | Feminine | Natural |38| Stone | `stone` | English | Irish | Masculine | Natural |39| Gleam | `gleam` | English | North American | Feminine | Natural |40| Meridian | `meridian` | English | North American | Masculine | Natural |41| Bossa | `bossa` | Portuguese | Brazilian | Feminine | Natural |42| Tempo | `tempo` | Portuguese | Brazilian | Masculine | Natural |43| Beacon | `beacon` | English | Filipino | Masculine | Generated |44| Delta | `delta` | English | Southern U.S. | Feminine | Generated |45| Cinder | `cinder` | English | Southern U.S. | Masculine | Generated |4647Regional influence describes a voice’s speaking style. Test the voice with the languages and pronunciation your application needs. For an approved voice created from your own recording, see [Custom voices](https://developers.openai.com/api/docs/guides/custom-voices).484950515253For WebSocket, choose `audio.format` at startup. The same format applies to input and output audio for the session. To use another format, start a new session. WebRTC negotiates its audio format during connection setup, so leave `audio.format` out of WebRTC requests. See [WebSocket audio formats](https://developers.openai.com/api/docs/guides/voice-websockets?api=live) for supported formats and streaming details.5455### Update a live session5657Use `session.update` for changes to `session.delegation.responses` in a session already using Responses delegation. Send only the settings you want to change; omitted settings retain their values. See [Configure Responses delegation](https://developers.openai.com/api/docs/guides/live-delegation#configure-responses-delegation) for the settings and update workflow.5859Choose the delegation mode and the fields `model`, `instructions`, `input`, `audio`, and `store` at startup. Use `session.update` only for the supported Responses settings described above; other configuration fields are rejected. To switch delegation modes, create a new session. At startup, `delegation: null` selects client delegation rather than restoring default Responses settings.6061A successful update emits `session.updated` with the resulting session configuration. Match it to your outgoing `event_id` through `client_event_id`. Handle [rejected commands](#handle-rejected-commands) in the same event loop. Track backend work and spoken output through their own events.6263## Provide history and context6465Use startup history to resume a topic, and append relevant context as the conversation continues. Keep trusted application instructions separate from user messages and factual results.6667### Seed a session with prior conversation6869Include prior text messages in `session.input` when you create the session. For example, add this `input` field to your [session creation configuration](https://developers.openai.com/api/docs/guides/live#connect-your-first-session):7071```javascript72```7374```python75from openai.types.live.session_config_param import SessionConfigParam7677session: SessionConfigParam = {78 "model": "gpt-live-1",79 "input": [80 {81 "type": "message",82 "role": "user",83 "content": [84 {"type": "input_text", "text": "I need help with my recent order."}85 ],86 },87 {88 "type": "message",89 "role": "assistant",90 "content": [{"type": "output_text", "text": "What is the order number?"}],91 },92 ],93}94```959697The list accepts up to 128 messages and 8,192 combined tokens. Each message has one text part and one of these roles: `developer`, `user`, or `assistant`. Developer and user messages use `input_text`; assistant messages use `text` or `output_text`. Put trusted application instructions in `instructions` or a developer message.9899Select the text history needed for the next interaction and supply it at startup. During the session, add updates with the context events below. Send backend-specific items, such as tool results, through the [delegation workflow](https://developers.openai.com/api/docs/guides/live-delegation).100101### Understand when context reaches the model102103Put any context the model needs from the start in `input`; the full field is available when the session starts.104105During a running session, `session.instructions.append`, `session.thinking.append`, and `session.commentary.append` add context over time. The acknowledgment arrives when the session timeline reaches the estimated end of the added context. Its `start_ms` and `end_ms` estimate where that update falls on the session timeline.106107These times describe context delivery, not speech or playback. The model may still respond before it has used the whole update. When an action depends on a new instruction or fact, verify the resulting behavior in your application.108109If the session timeline stops, the acknowledgment can remain pending. Match acknowledgments to the outgoing `event_id` through `client_event_id`, and keep handling errors while you wait. Closing the session returns errors for appends that are still pending.110111### Add context during the conversation112113Choose an event based on how the model should use the update:114115- `session.instructions.append`: add trusted application instructions that influence behavior and speech.116- `session.thinking.append`: add factual context without asking the model to say it immediately.117- `session.commentary.append`: provide information for the model to say aloud, which it may paraphrase.118119Each event takes plain-string `content` of up to 500 tokens and a required `delegation_id`. Use `null` for session-wide context. For example, send this after your application has verified the user's acceptance and started the lookup:120121```javascript122export function sendUpdate(connection) {123 connection.send({124 type: "session.thinking.append",125 event_id: "context_1",126 delegation_id: null,127 content:128 "The user has already accepted the terms. The account lookup is still running.",129 });130}131```132133```python134from openai.resources.live.live import AsyncLiveConnection135from openai.resources.live.sideband import AsyncSidebandConnection136137138async def send_update(139 connection: AsyncLiveConnection | AsyncSidebandConnection,140) -> None:141 await connection.session.thinking.append(142 event_id="context_1",143 delegation_id=None,144 content=(145 "The user has already accepted the terms. The account lookup is still "146 "running."147 ),148 )149```150151152Handle `session.thinking.appended` with `client_event_id: "context_1"`, or the corresponding error, to track this update. See [Understand when context reaches the model](#understand-when-context-reaches-the-model) for acknowledgment timing.153154The assistant may repeat information supplied through any of these events. Send only information suitable for the conversation, and keep credentials and secrets in your backend. Use `session.instructions.append` for behavior defined by your application. Supply factual tool results as context, and enforce permissions and required confirmations in application code.155156For page navigation, selections, and other UI changes, see [Share UI context](https://developers.openai.com/api/docs/guides/live-delegation#share-ui-context) for concise updates that help GPT-Live understand what the user is referring to.157158For an update about a specific backend task, use the ID of the relevant client delegation. A delegation ID identifies the Live task; Responses response IDs and tool call IDs identify different objects. See [Send the right kind of update](https://developers.openai.com/api/docs/guides/live-delegation#send-the-right-kind-of-update) for the workflow.159160When your application detects a problem, send a short correction through the session’s primary WebSocket or a [sideband WebSocket](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#decide-whether-you-need-a-sideband). See [Apply conversation guardrails](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#apply-conversation-guardrails) for checks, action controls, and playback handling.161162163164165166167168169170171172173## Manage speech and transcripts174175### Transcript deltas176177Listen for `session.input_transcript.delta` for user speech and `session.output_transcript.delta` for assistant speech. Each event contains a text fragment and its interval on the session timeline:178179```json180{181 "type": "session.input_transcript.delta",182 "event_id": "event_transcript_1",183 "delta": "What is",184 "start_ms": 1000,185 "end_ms": 1200186}187```188189Append each speaker’s `delta` fragments exactly as received, preserving spaces and repeated words. Retain their `start_ms` and `end_ms`. These values are milliseconds from the start of the session. The example above covers the interval from 1,000 ms up to, but excluding, 1,200 ms. They describe approximate fragment timing rather than exact word boundaries; use them instead of packet arrival times to organize the transcript.190191Transcript events arrive for intervals that contain text, and delivery can be uneven. A fragment may contain only part of a sentence; a gap in delivery may be a network delay. Transcript deltas have no item ID or event that marks a completed conversational turn, so your application decides how to group them for display.192193Processing transcript fragments is optional. You can use them to update your UI, run checks, or start work early while the conversation continues. For lightweight checks, consider a small model such as `gpt-5.6-luna` with low reasoning effort. See [React to transcript fragments](https://developers.openai.com/api/docs/guides/live-delegation#react-to-transcript-fragments) for examples and connection guidance.194195Use [transcript guardrails](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#run-checks-alongside-the-conversation) to monitor the conversation and trigger interventions while speech continues. If your application needs to check assistant speech before playback, see [Check speech before playback](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#check-speech-before-playback) for buffering, approval, interruption, and recovery handling.196197198199200201Keep transcript timing separate from audio playback. WebSocket `session.output_audio.delta` events have no timing fields or output-audio-done event; WebRTC delivers audio through its media track. See [Connections](https://developers.openai.com/api/docs/guides/voice-websockets?api=live) for audio handling.202203204205206207### Display captions208209GPT-Live is full duplex: the caller and assistant can speak at the same time. Update their captions independently so both speakers’ text can keep growing during overlapping speech.210211If your app uses chat bubbles, the fragments “I’d like” and “ to change my booking” can appear in one caller bubble. If the assistant says “Sure” while the caller continues, show that acknowledgment separately while allowing the caller’s bubble to keep growing. Keep the original fragments and timestamps so text that arrives later can update the appropriate bubble.212213Use `session.output_transcript.delta` for spoken captions and show backend updates separately. Keep decisions about running tools or canceling work in your application’s task logic, separate from how you group text for display.214215### Control microphone input216217Send `session.input_audio.mute` to mute input without ending the session:218219```javascript220export function sendUpdate(connection) {221 connection.send({222 type: "session.input_audio.mute",223 event_id: "mute_1",224 });225}226```227228```python229from openai.resources.live.live import AsyncLiveConnection230from openai.resources.live.sideband import AsyncSidebandConnection231232233async def send_update(234 connection: AsyncLiveConnection | AsyncSidebandConnection,235) -> None:236 await connection.session.input_audio.mute(237 event_id="mute_1",238 )239```240241242Wait for `session.input_audio.muted` with `client_event_id: "mute_1"` before treating the command as accepted. To resume input, send `session.input_audio.unmute` and wait for `session.input_audio.unmuted`. Handle errors for either command.243244Muting input leaves the session running: the model can keep generating speech, and delegated work can continue. Use your application’s microphone capture and audio player controls when you also need to stop local recording or playback.245246### Greet before the caller speaks247248To have GPT-Live open the conversation, send greeting instructions after `session.started`. Specify the language, what the assistant should say, and that it should begin immediately, then pause to listen. Use the application’s chosen greeting language until the caller speaks. For example:249250> Greet the caller now in English. Introduce yourself as the support assistant and ask how you can help. Then pause and listen.2512521. Keep input audio running throughout this sequence, including silence before the caller speaks. On WebSocket, continue sending `session.input_audio.append`; on WebRTC, keep the input audio track active.2532. Send the instructions once with `session.instructions.append` and `delegation_id: null`.2543. Match `session.instructions.appended` to your command using `client_event_id`, and handle any error. This acknowledgment confirms that the instructions were accepted.255256For exact wording and a known playback-completion point, play a verified recording or rendered clip through your application and [control GPT-Live playback while it plays](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#control-playback-when-needed). Test greetings in the languages you support, including when the caller starts speaking during the greeting. See [Prompting voice models](https://developers.openai.com/api/docs/guides/live-prompting) for prompt design.257258### Deliver a disclosure259260Use `session.instructions.append` to request specific spoken wording for a disclosure. `session.commentary.append` may paraphrase the text. After `session.started`, for example, send:261262```javascript263export function sendUpdate(connection) {264 connection.send({265 type: "session.instructions.append",266 event_id: "disclosure_1",267 delegation_id: null,268 content:269 "Immediately say the following disclosure exactly and in full before responding to the caller: This call may be recorded for quality and training purposes.",270 });271}272```273274```python275from openai.resources.live.live import AsyncLiveConnection276from openai.resources.live.sideband import AsyncSidebandConnection277278279async def send_update(280 connection: AsyncLiveConnection | AsyncSidebandConnection,281) -> None:282 await connection.session.instructions.append(283 event_id="disclosure_1",284 delegation_id=None,285 content=(286 "Immediately say the following disclosure exactly and in full before "287 "responding to the caller: This call may be recorded for quality and "288 "training purposes."289 ),290 )291```292293294Keep input audio running, as in [Greet before the caller speaks](#greet-before-the-caller-speaks). An instruction sent during the conversation can interrupt speech in progress.295296Check the generated disclosure and its playback before marking it delivered. The instruction acknowledgment records acceptance; use the audio itself to check the wording. For exact wording and a known playback-completion point, play a verified recording or rendered clip through your application and control GPT-Live output while it plays. See [Control playback when needed](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#control-playback-when-needed).297298299300301302## Manage longer conversations303304GPT-Live manages long conversations automatically and preserves your original startup instructions.305306The default context window holds 128,000 tokens, including your instructions, conversation text, and audio tokens that don’t appear in the transcript.307308GPT-Live summarizes older conversation history in the background. When context usage exceeds 90%, it starts a replacement voice engine within the same session. The replacement receives your original instructions and up to 8,192 tokens of conversation history, containing recent messages and, when available, a summary of older messages. Preparing a summary does not immediately change the running engine’s context.309310311312313314Older conversation details may be summarized or omitted. Keep important facts, confirmed actions, and current task state in your application, and provide relevant context when needed.315316## Store and fork a session317318A fork starts a new session from a saved voice conversation. Use it to run several evaluation trials from the same reference conversation, or to let a user continue after an earlier session has ended. Each fork gets a new connection and session ID, with the source conversation and its saved configuration as its starting point.319320### Run evaluations from a reference conversation321322Suppose you want to test how your agent handles a caller changing an order. Record the setup once, through the point where the caller has identified the order. End and finalize that session before the caller asks to change it. Each evaluation can then fork the same reference session and receive the same next caller audio: “Actually, can you send it to my office instead?”323324For each trial, restore the same test order and application state, supply the next caller input, and evaluate the new response and tool actions. You can repeat the scenario or compare supported Responses backend settings. Measure fork startup separately from response time. See the [GPT-Live evaluation guide](https://developers.openai.com/cookbook/examples/audio/voice_agent_evaluation) for choosing scenarios and measuring results.325326Forks inherit the GPT-Live model, voice, and original instructions. To compare a different voice model or startup prompt, create new sessions with that configuration. The fork API uses the completed source recording, so end the reference session where you want the evaluation to begin.327328### Continue after a session ends329330For example, a caller may hang up and call back later, or reconnect after a dropped call. If the earlier session has a completed stored recording, your application can fork it on a new connection and continue from the saved conversation.331332Save application task state alongside the source session ID. Before continuing, check the status of any outstanding backend work and give the new session its current results. For example, if an order update was already submitted, confirm its outcome before attempting another update. Use the new session ID for controls and sideband connections, and route subsequent backend results to the new session.333334### Prepare a session for forking3353361. **Enable storage when you create the source.** Set `store: true` in its session configuration. Storage defaults to `false`, must be enabled for your project, and requires a data policy that permits persistence.3372. **Save the source session ID.** Read it from `session.started` or the WebRTC creation response, and associate it with your application’s conversation record.3383. **Finish and close the source.** Complete required backend work, then follow [Usage and graceful close](https://developers.openai.com/api/docs/guides/live-conversations#usage-and-graceful-close). Keep the connection open until `session.closed` and handle any finalization error. Forking requires a completed stored recording; saving it can add time to finalization.3394. **Start a fork on a new connection.** Use the source ID with the transport flow below, save the new session ID, and complete startup before continuing the conversation. Set the child’s `store` explicitly: `true` if you want to fork its continuation later, or `false` if you do not need to store that trial. Omitting it inherits the source setting.340341Stored recordings are available for 30 days. With Zero Data Retention (ZDR), `store` is treated as `false` and forking is unavailable. For fork-based evaluations, use a non-ZDR organization with storage enabled for the project.342343If you have no completed stored recording, [start a new session with relevant saved text history](https://developers.openai.com/api/docs/guides/live-conversations#seed-a-session-with-prior-conversation). See [GPT-Live data controls](https://developers.openai.com/api/docs/guides/your-data#v1livesessions) for storage requirements.344345For example, set this field in the source session’s WebSocket `session.start` configuration or WebRTC creation request:346347```json348{349 "store": true350}351```352353Start the fork through the transport your application uses:354355| Transport | Start the fork |356| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |357| WebSocket | Connect to `wss://api.openai.com/v1/live/sessions/{source_session_id}/fork`. |358| WebRTC | Send a new SDP offer to `POST /v1/live/sessions/{source_session_id}/fork`. Apply the returned `transport.sdp` answer to the new peer connection. |359360A fork inherits the source session’s model, original instructions, and input. Send only the supported overrides at startup:361362- **WebSocket:** `store`, Responses delegation settings, and the new connection’s `audio.format`. Send a `session.start` event with a `session` object; use `{}` to keep inherited settings where supported.363- **WebRTC:** `store`, Responses delegation settings, and frontend client permissions.364365For a WebSocket fork, set `audio.format` for the new connection or use the default PCM16 at 24 kHz. The source audio format and frontend data-channel permissions are not inherited. WebRTC negotiates audio format during connection setup; omit `audio.format`. WebRTC preserves frontend permission settings unless you override them.366367For WebSocket, wait for `session.started` before sending more commands. For WebRTC, the HTTP request starts the session; continue through the negotiated connection without sending another `session.start`.368369### Start a WebSocket fork370371Set `OPENAI_API_KEY`. The examples use the stored source session ID saved by your application. They confirm startup and then close the fork. To continue the conversation, send and receive audio after `session.started` using the [WebSocket connection flow](https://developers.openai.com/api/docs/guides/voice-websockets?api=live). See the [fork WebSocket reference](https://developers.openai.com/api/reference/resources/live/fork-websocket) for the startup fields and events.372373```javascript374import OpenAI from "openai";375import { ForksWS } from "openai/resources/live/forks/ws";376377async function forkSession(sourceSessionId) {378 const ws = new ForksWS(new OpenAI(), { session_id: sourceSessionId });379 let finalized = false;380 try {381 for await (const event of ws) {382 if (event.type === "open") {383 ws.send({ type: "session.start", session: {} });384 } else if (event.type === "error") {385 throw event.error;386 } else if (event.type === "message") {387 if (event.message.type === "session.started") {388 console.log("Fork ready:", event.message.session.id);389 // This startup example closes the fork after confirming it is ready.390 ws.send({ type: "session.close" });391 } else if (event.message.type === "session.closed") {392 console.log("Final usage:", event.message.usage);393 finalized = true;394 break;395 }396 }397 }398 if (!finalized) throw new Error("Connection closed before session.closed");399 } finally {400 ws.close();401 }402}403```404405```python406from openai import OpenAI407408409def fork_session(source_session_id: str) -> None:410 client = OpenAI()411 with client.live.forks.connect(session_id=source_session_id) as connection:412 connection.session.start(session={})413 finalized = False414 for event in connection:415 if event.type == "session.started":416 print("Fork ready:", event.session.id)417 # This startup example closes the fork after confirming it is ready.418 connection.session.close()419 elif event.type == "session.closed":420 print("Final usage:", event.usage)421 finalized = True422 break423 elif event.type == "error":424 raise RuntimeError(event.error.message)425 if not finalized:426 raise RuntimeError("Connection closed before session.closed")427```428429430### Start a WebRTC fork431432Create a new SDP offer in your frontend and send it to your backend. The following backend examples use that offer and the stored source session ID from your application:433434```javascript435import OpenAI from "openai";436437async function forkSession(sourceSessionId, offerSdp) {438 const client = new OpenAI();439 const fork = await client.live.sessions.fork(sourceSessionId, {440 transport: { type: "webrtc", sdp: offerSdp },441 });442 console.log(JSON.stringify(fork));443}444```445446```python447from openai import OpenAI448449450def fork_session(source_session_id: str, offer_sdp: str) -> None:451 client = OpenAI()452 fork = client.live.sessions.fork(453 source_session_id,454 transport={"type": "webrtc", "sdp": offer_sdp},455 )456 print(fork.model_dump_json())457```458459460Return the response to your frontend, apply `transport.sdp` as the new peer connection's answer, and retain the new `session.id`. Keep the API key on your backend.461462Use the new session ID for sideband connections and session controls. Before retrying an unfinished action, check its outcome in your backend and restore the current application task state. If you have no completed stored recording, [seed a new session with saved history](#seed-a-session-with-prior-conversation).463464### Download a recording465466After the stored recording is finalized, download its audio with `GET /v1/live/sessions/{session_id}/content`. The response is binary stereo WAV, with input audio in the left channel and output audio in the right channel. The examples use the stored session ID from your application and stream the response to `recording.wav`:467468```javascript469import OpenAI from "openai";470import { createWriteStream } from "node:fs";471import { pipeline } from "node:stream/promises";472473async function downloadRecording(sessionId) {474 const client = new OpenAI();475 const response = await client.live.sessions.downloadRecording(sessionId);476 if (!response.body) throw new Error("Recording response has no body");477 await pipeline(response.body, createWriteStream("recording.wav"));478}479```480481```python482from openai import OpenAI483484485def download_recording(session_id: str) -> None:486 client = OpenAI()487 with client.live.sessions.with_streaming_response.download_recording(488 session_id489 ) as response:490 response.stream_to_file("recording.wav")491```492493494## Close idle sessions and resume495496For applications with long gaps between interactions, close the voice session during inactivity and start a new session when the user returns. Keep conversation context and application task state so the user can continue without repeating themselves. For example, an in-car assistant can resume when the driver activates voice again, while a coding assistant can keep its backend worker running between voice conversations.4974981. **Decide when to close.** Use an application-controlled inactivity timeout based on audio activity, assistant playback, and application interactions. Allow for expected pauses, such as reading or thinking. Close only when playback has finished and no pending work requires the current voice session. Gaps between transcript events alone do not establish silence.4992. **Save state and close gracefully.** Save the source session ID, conversation context, and current task state. Finish any required Responses work, then follow [Usage and graceful close](#usage-and-graceful-close): install the `session.closed` listener, send `session.close`, and wait for `session.closed` before releasing the connection. With client delegation, application-managed backend work can continue independently while voice is closed.5003. **Detect when to restart.** Offer a button labeled **Resume conversation**, a push-to-talk control, or an application-managed wake trigger. A closed Live session cannot listen for the user. If you use local speech detection to restart automatically, keep microphone capture active and buffer the opening speech through connection setup. Deliver that audio once the new session is ready, so the user’s first words are preserved.5014. **Restore context in a new session.** If the source was created with `store: true`, storage is enabled and permitted, and its recording finalized successfully, [fork the stored session](#prepare-a-session-for-forking). Otherwise, [start a new session with saved text history](#seed-a-session-with-prior-conversation). Save the new session ID, check the status of outstanding backend operations, and route subsequent results to the new session. Keep operation status in your application so restarting does not repeat completed actions.502503Muting the microphone leaves the session active. Choose an idle timeout by comparing avoided voice duration with session-creation costs and the delay before voice becomes ready again. See [Voice session costs](https://developers.openai.com/api/docs/guides/voice-latency-cost?api=live#voice-session-costs) and [WebRTC initialization charges](https://developers.openai.com/api/docs/guides/voice-latency-cost?api=live#webrtc-initialization-charges).504505## Handle errors and end the session506507Keep reading session events until the session finalizes. Distinguish a rejected command, a failed connection, and a completed session so your application can recover appropriately.508509### Handle rejected commands510511Read `error` events alongside acknowledgments. When present, `error.client_event_id` identifies the outgoing command that failed:512513```json514{515 "type": "error",516 "event_id": "event_error",517 "error": {518 "type": "invalid_request_error",519 "code": "immutable_field_update",520 "message": "The delegation type cannot change after session startup.",521 "param": "session.delegation.type",522 "client_event_id": "event_update"523 }524}525```526527Provide a general error handler for errors whose code is `null` or whose client event ID is absent. For an immutable-field error, keep the current configuration or create a new session with the intended settings.528529### Handle moderation530531Moderation can affect the session in two ways:532533- Some moderation events end the session.534- Others cut off assistant audio for the remainder of its current speech and emit an `error` event without ending the session.535536Keep handling `error` events while audio is playing. Track audio interruption and session closure separately, and mark a spoken message as delivered only after checking its playback. Apply your own [conversation guardrails](https://developers.openai.com/api/docs/guides/voice-server-controls?api=live#apply-conversation-guardrails) alongside built-in moderation.537538### Usage and graceful close539540`session.usage.updated` reports cumulative voice duration in seconds:541542```json543{544 "type": "session.usage.updated",545 "event_id": "event_usage_1",546 "usage": { "seconds": 12 },547 "context_window": { "usage_ratio": 0.42 }548}549```550551Use the latest `usage.seconds` as the running total for voice duration. For example, updates of 12 and then 15 seconds mean 15 seconds of use. Track backend token usage separately from nested Responses completion events. See [Cost optimization](https://developers.openai.com/api/docs/guides/voice-latency-cost?api=live) for usage accounting.552553To close gracefully:5545551. Finish any delegated Responses work your application needs, including pending function results and response continuations.5562. Install the `session.closed` listener before sending `session.close`.5573. Send `session.close` and stop submitting new work to the session. Keep the WebSocket or WebRTC connection, data channel, and any attached sideband receiver alive while pending session events drain.5584. Read the final `usage.seconds`, `reason`, and session snapshot from `session.closed`. Preserve delegated usage already received through `response.event`.5595. Clean up transports and audio devices after that event. If finalization fails or exceeds a timeout your application sets, report incomplete finalization and release the resources.560561Sending `session.close` cancels queued Responses and rejects further commands. An active response can finish, but one waiting for a function result cannot continue after closing starts. Decide separately whether to finish or cancel work your application runs through client delegation.562563Use `session.closed` to confirm finalization and read the final configuration snapshot. Keep the transport open until this event arrives. If the socket closes first, record finalization as unconfirmed; if it closes after a valid `session.closed`, retain the confirmed result.564565The final event's `reason` explains why the session ended:566567| Reason | Meaning |568| ----------------- | -------------------------------------------------------------------- |569| `close_requested` | Your application sent `session.close` or called the hangup endpoint. |570| `expired` | The session reached its duration limit. |571| `content` | A safety filter ended the session. |572| `remote_hangup` | The remote primary connection ended gracefully. |573| `connection_lost` | The primary or upstream connection was lost unexpectedly. |574575A `session.closed` event confirms finalization even when the reason is a connection loss or safety termination. Without that event, final usage remains unconfirmed. A stored session can take longer to finalize while its recording is saved; choose an application timeout that accounts for storage.576577### Recover from a failed connection578579An HTTP session-creation error means the session did not reach `session.started`. Handle startup errors separately from errors in a running session. If a running connection fails before `session.closed`, retain the latest observed usage and mark final usage as unconfirmed.580581If a completed stored recording is available, [fork it](#store-and-fork-a-session) to continue in a new session. Otherwise, create a replacement session with relevant saved history. Before continuing, check unfinished actions with your backend, restore current task state, and update result routing so late results from the previous session cannot overwrite newer work.