Skip to navigation

Live voice

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

openai/gpt-live-1 is a full-duplex voice model that listens and speaks at once, for voice agents and live assistants. Gateway relays the OpenAI Live protocol at wss://api-gateway.merge.dev/v1/live/sessions unchanged, so the OpenAI Live event reference applies as written.

Connect

Send your Gateway key on the handshake (so connect from your server), then send session.start with the model, which goes in that event, not the URL. Wait for session.started before sending audio.

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", "audio": {"format": {"type": "audio/pcm", "rate": 24000}}},
}))
async for frame in ws:
event = json.loads(frame)
if event["type"] == "session.started":
chunk = base64.b64encode(bytes(960)).decode() # 20 ms of 24 kHz PCM16; stream your mic here
await ws.send(json.dumps({"type": "session.input_audio.append", "audio": chunk}))
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
asyncio.run(main())

Audio travels as base64 PCM in JSON text frames. To end a session, send session.close and read until session.closed, which carries the final usage.seconds.

Delegation

Client delegation is the default: your app receives session.delegation.created and answers with session.commentary.append or session.thinking.append. With "delegation": {"type": "responses", "responses": {"model": "..."}}, OpenAI runs the backend model and streams its events as response.event. Gateway checks that your organization and key may use that model at session.start and on any session.update that changes it.

Pricing

Sessions bill per second at the route’s input_per_second rate, with no rounding to a whole minute. The billed time is OpenAI’s usage.seconds, including silence and delegated work. If your client disconnects without session.close, Gateway closes the session upstream and still bills the final duration. Backend-model tokens under Responses delegation bill separately at that model’s rates.

Reference

LimitValue
Maximum session length2 hours, or OpenAI’s session.started.session.expires_at if sooner
Idle timeout (no events from your client)15 minutes
Time to send session.start30 seconds
Maximum frame size16 MB
Concurrent sessionsThe executing OpenAI account’s Live limit

At a limit, Gateway asks OpenAI to finalize (so you still get session.closed with final usage), then closes with 4408. A refused handshake (invalid key, exhausted budget, rate limit) returns an HTTP status; a refusal after the socket opens sends an error event, then closes with 4000 plus the HTTP status:

Close codeCause
4400Not a Live model (unsupported_endpoint_for_model), an @alias/... model (alias_not_supported), session.store true under zero data retention (store_not_allowed), no session.start in time (session_start_timeout), or another bad request
4401, 4402, 4403Unauthorized, out of budget, or a model blocked for your organization or key (model_not_allowed)
4404Unknown model
4408Session limit reached
4502Upstream connection failed (upstream_unavailable)

WebRTC, SIP telephony, sideband connections, session forking, and recording download aren’t available through Gateway.

Next steps