Live voice

Full-duplex voice sessions with OpenAI GPT-Live over a WebSocket

openai/gpt-live-1 is a full-duplex voice model: it listens and speaks at the same time. Gateway relays the native OpenAI Live protocol over a WebSocket at /v1/live/sessions, using your Gateway API key and the route’s OpenAI credential (Merge-managed or your own key). Events pass through unchanged, so the OpenAI Live event reference applies as written.

Connect

URLwss://api-gateway.merge.dev/v1/live/sessions
AuthAuthorization: Bearer $MERGE_GATEWAY_API_KEY on the handshake
First eventsession.start with session.model set to openai/gpt-live-1
AudioBase64 PCM in JSON text frames, 24 kHz mono PCM16 by default

The model goes in session.start, not in the URL. Wait for session.started before sending audio or context. The connection is server-side only: Gateway authenticates the WebSocket handshake with your API key, so do not open it from a browser.

Python
import asyncio, base64, json, os
import websockets
async def main():
async with websockets.connect(
"wss://api-gateway.merge.dev/v1/live/sessions",
additional_headers={"Authorization": f"Bearer {os.environ['MERGE_GATEWAY_API_KEY']}"},
) as ws:
await ws.send(json.dumps({
"type": "session.start",
"session": {
"model": "openai/gpt-live-1",
"instructions": "Keep replies brief.",
"audio": {"format": {"type": "audio/pcm", "rate": 24000}},
"delegation": {"type": "client"},
},
}))
async for frame in ws:
event = json.loads(frame)
if event["type"] == "session.started":
# 960 bytes = 20 ms of 24 kHz PCM16. Stream your microphone here.
silence = base64.b64encode(bytes(960)).decode()
for _ in range(50):
await ws.send(json.dumps({"type": "session.input_audio.append", "audio": silence}))
await ws.send(json.dumps({"type": "session.close"}))
elif event["type"] == "session.output_audio.delta":
pass # base64 PCM16 to play back
elif event["type"] == "session.closed":
print("billed seconds:", event["usage"]["seconds"])
break
elif event["type"] == "error":
print(event["error"])
asyncio.run(main())

To end a session, send {"type": "session.close"} and keep reading until session.closed, which carries the final usage.seconds.

Delegation

Both delegation modes are relayed:

  • Client delegation ("delegation": {"type": "client"}, or omitted): your application receives session.delegation.created and answers with session.commentary.append or session.thinking.append
  • Responses delegation ("delegation": {"type": "responses", "responses": {"model": "..."}}): OpenAI runs the backend model and streams its events as response.event. The backend model must be one your organization and key may use, both at session.start and on any session.update that changes it.

Pricing

Voice sessions bill per second at the route’s per-second rate (unit: per_second on GET /v1/models). OpenAI’s list price is 0.05perminute,withnoroundinguptoawholeminute:a90secondsessioncosts0.05 per minute, with no rounding up to a whole minute: a 90-second session costs 0.075. The billed duration is OpenAI’s reported usage.seconds from session.closed, and it includes silence, a muted microphone, and time spent waiting on delegated work.

If your client disconnects without session.close, Gateway closes the session upstream and bills the final reported duration. With Responses delegation, the backend model’s tokens bill separately at that model’s own token rates, one usage record per backend response.

Limits

LimitDefault
Maximum session length2 hours, or OpenAI’s own expiry (session.started.session.expires_at) if that comes first
Idle timeout (no events from your client)15 minutes
Time to send session.start after connecting30 seconds
Concurrent sessionsThe executing OpenAI account’s Live concurrency limit

When Gateway ends a session at a limit, it asks OpenAI to finalize first, so you still receive session.closed and the final usage, then closes the WebSocket with code 4408.

A refused handshake (invalid key, exhausted budget, rate limit) returns the HTTP status before the WebSocket opens. A refusal after the connection opens (unknown or blocked model, a model that is not a Live model) arrives as a Live error event, followed by a close code of 4000 plus the equivalent HTTP status, for example 4403 for a blocked model.

Organizations with zero data retention must leave session.store false.

WebRTC, SIP telephony, sideband connections, session forking, and recording download are not available through Gateway.

Next steps