Elige la API que usa tu aplicación. Cada API tiene su propia autenticación, creación de sesiones y contrato de eventos.
Conectar un navegador a GPT-Live
Usa WebRTC para aplicaciones de voz en navegadores. La entrada del micrófono y la voz generada se transmiten por pistas multimedia negociadas. Un canal de datos transporta eventos JSON para las transcripciones, las actualizaciones de sesión y el trabajo delegado.
Tu navegador crea una oferta del protocolo de descripción de sesiones (SDP). El servidor de tu aplicación la intercambia por una respuesta mediante POST /v1/live/sessions, usando la clave de API del proyecto. Mantén la clave y la configuración de la sesión en tu servidor de confianza.
Antes de empezar
Necesitas:
- Una clave de API del proyecto con acceso a GPT-Live.
- Un entorno de ejecución de servidor para el ejemplo del SDK que elijas. El ejemplo de Node.js requiere Node.js 22.6 o posterior.
- Un navegador con permiso para usar el micrófono, que funcione con HTTPS o localhost.
El ejemplo usa la delegación a Responses con gpt-5.6-terra y la búsqueda web alojada. Para obtener instrucciones del backend y herramientas de la aplicación, consulta Delegación y herramientas. Para conocer el uso de voz y del backend, consulta Optimización de costos.
Entender la secuencia de conexión
- Solicita acceso al micrófono a partir de una acción del usuario y agrega sus pistas a una conexión entre pares.
- Crea el canal de datos y registra los escuchadores de eventos antes de crear la oferta SDP.
- Establece la descripción local, espera a que se recopilen los candidatos ICE y envía la oferta a tu servidor.
- Haz que tu servidor envíe a OpenAI una solicitud POST con JSON que contenga
sessionytransport: { type: "webrtc", sdp: ... }. - Aplica la respuesta SDP recibida como descripción remota. Espera a recibir
session.starteden el canal de datos antes de enviar comandos de la aplicación.
La solicitud HTTP inicia la sesión. No envíes session.start por el canal de datos. La cadena oai-events del ejemplo es la etiqueta del canal de datos.
Al crear una sesión WebRTC con POST /v1/live/sessions, se facturan 15 segundos de duración de voz durante la inicialización. Ese importe se descuenta de los cargos por duración una vez que la sesión comienza a ejecutarse; no se agregan 15 segundos adicionales a la sesión en curso. Consulta Cargos de inicialización de WebRTC para conocer el cálculo de costos.
Crear el servidor de la aplicación
Guarda el ejemplo del servidor en un directorio nuevo y configura OPENAI_API_KEY en su entorno. Para Node.js, usa server.mjs e instala openai y express con npm install openai express. Para Python, instala openai. Este ejemplo escucha en 127.0.0.1, acepta solicitudes de sesión de http://localhost:3000 y sirve index.html desde el directorio donde lo ejecutas.
Elige un lenguaje de servidor a continuación; cada variante sirve index.html y el mismo punto de acceso /api/session en el puerto 3000. Usa una versión del SDK compatible con Live. Ejecuta solo una variante a la vez.
import express from "express";
import OpenAI from "openai";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
const app = express();
const client = new OpenAI({ maxRetries: 0 });
const port = 3000;
const origin = `http://localhost:${port}`;
const indexPath = resolve("index.html");
app.use(express.json({ limit: "64kb" }));
app.get("/", async (_request, response) => {
response.type("html").send(await readFile(indexPath, "utf8"));
});
// Local-only demo. Add your application's authentication and authorization
// before exposing session creation to other users.
app.post("/api/session", async (request, response) => {
if (request.headers.origin !== origin) {
response.status(403).json({ error: "Unexpected request origin" });
return;
}
if (typeof request.body?.sdp !== "string" || !request.body.sdp.trim()) {
response.status(400).json({ error: "An SDP offer is required" });
return;
}
if (!process.env.OPENAI_API_KEY) {
response.status(503).json({ error: "Set OPENAI_API_KEY on the server" });
return;
}
try {
const result = await client.live.create({
session: {
model: "gpt-live-1",
instructions:
"Be concise. Delegate requests needing current information to the backend, which can search the web.",
delegation: {
type: "responses",
responses: {
model: "gpt-5.6-terra",
instructions:
"Use web search when current facts are needed. Return concise, grounded results for a spoken conversation.",
tools: [{ type: "web_search" }],
tool_choice: "auto",
},
},
},
transport: {
type: "webrtc",
sdp: request.body.sdp,
},
});
// Preserve the SDK's typed session ID and SDP answer.
response.status(201).json(result);
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.error("Live session creation failed", error.status);
response
.status(error.status ?? 502)
.json({ error: "Live session creation failed" });
}
});
app.listen(port, "127.0.0.1", () => console.log(`Open ${origin}`));Antes de permitir que otros usuarios accedan al servidor, protege /api/session con la autenticación, la autorización y los límites de solicitudes de tu aplicación, además de HTTPS. La comprobación de origen de este ejemplo local no autentica a los usuarios.
Crear el cliente del navegador
Crea index.html en el directorio donde ejecutas el servidor:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GPT-Live connection</title>
</head>
<body>
<script type="module">
// Paste the browser code below here.
</script>
</body>
</html>Pega el siguiente código dentro del script de módulo. Agrega controles de inicio y finalización, conecta el micrófono y el audio de salida, y maneja los eventos de sesión. /api/session es una ruta del servidor de tu aplicación.
const start = document.createElement("button");
start.textContent = "Start conversation";
const stop = document.createElement("button");
stop.textContent = "End conversation";
stop.disabled = true;
const status = document.createElement("p");
const audio = new Audio();
audio.autoplay = true;
audio.controls = true;
document.body.append(start, stop, status, audio);
let peer;
let events;
let microphone;
let closeTimeout;
let ready = false;
let finalized = false;
function cleanup() {
clearTimeout(closeTimeout);
microphone?.getTracks().forEach((track) => track.stop());
events?.close();
peer?.close();
audio.srcObject = null;
ready = false;
start.disabled = false;
stop.disabled = true;
}
start.addEventListener("click", async () => {
start.disabled = true;
finalized = false;
status.textContent = "Connecting…";
try {
const connection = new RTCPeerConnection();
peer = connection;
connection.addEventListener("track", (event) => {
audio.srcObject = new MediaStream([event.track]);
audio.play().catch(() => {
status.textContent =
"Select play on the audio controls to hear the assistant.";
});
});
microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
for (const track of microphone.getAudioTracks()) {
connection.addTrack(track, microphone);
}
// Create the event channel before creating the SDP offer.
events = connection.createDataChannel("oai-events");
events.addEventListener("message", ({ data }) => {
const event = JSON.parse(data);
if (event.type === "session.started") {
ready = true;
stop.disabled = false;
status.textContent = "Connected: " + event.session.id;
} else if (event.type === "session.closed") {
finalized = true;
console.log("Final session usage", event.usage);
status.textContent = "Conversation ended.";
cleanup();
} else {
// Save transcript and nested Responses events as needed by your app.
console.log(event);
}
});
events.addEventListener("close", (event) => {
if (event.target !== events) return;
if (!finalized) {
status.textContent = "Disconnected without final session usage.";
cleanup();
}
});
const offer = await connection.createOffer();
await connection.setLocalDescription(offer);
if (connection.iceGatheringState !== "complete") {
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
connection.removeEventListener("icegatheringstatechange", onState);
reject(new Error("Timed out while gathering ICE candidates"));
}, 10_000);
function onState() {
if (connection.iceGatheringState !== "complete") return;
clearTimeout(timeout);
connection.removeEventListener("icegatheringstatechange", onState);
resolve(undefined);
}
connection.addEventListener("icegatheringstatechange", onState);
onState();
});
}
const sdp = connection.localDescription?.sdp;
if (!sdp) throw new Error("Missing local SDP offer");
const response = await fetch("/api/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sdp }),
});
if (!response.ok) throw new Error(await response.text());
const result = await response.json();
console.log("Created session", result.session.id);
await connection.setRemoteDescription({
type: "answer",
sdp: result.transport.sdp,
});
// The HTTP request started this session. Do not send session.start here.
} catch (error) {
status.textContent =
error instanceof Error ? error.message : String(error);
cleanup();
}
});
stop.addEventListener("click", () => {
if (!ready || !events || events.readyState !== "open") return;
stop.disabled = true;
status.textContent = "Finishing the conversation…";
// The session.closed handler is already registered. Keep media and events
// alive while pending work drains; only clean up after the final event.
events.send(JSON.stringify({ type: "session.close" }));
closeTimeout = setTimeout(() => {
status.textContent = "Incomplete finalization: no session.closed event.";
cleanup();
}, 15_000);
});Ejecuta el servidor que elegiste (node server.mjs o python server.py), abre http://localhost:3000 y selecciona Iniciar conversación. Cuando el estado cambie a Conectado, haz una pregunta que requiera información actual para probar la búsqueda alojada. Usa los controles de audio si tu navegador bloquea la reproducción automática.
Leer la respuesta de la sesión
Una solicitud exitosa devuelve HTTP 201 con JSON que contiene el ID de la sesión y la respuesta SDP:
{
"session": { "id": "live_123" },
"transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}Lee result.session.id y pasa result.transport.sdp a setRemoteDescription. Trata el ID de la sesión como un valor opaco y consérvalo sin cambios, incluido su prefijo.
Manejar contenido multimedia y eventos
Envía el audio del micrófono y recibe la voz generada a través de las pistas multimedia. WebRTC negocia el formato de audio mediante SDP, así que omite audio.format de la configuración de la sesión. No envíes session.input_audio.append ni esperes recibir session.output_audio.delta en el canal de datos.
Usa el canal de datos para los incrementos de transcripción, los comandos de sesión y los mensajes response.event anidados. Consulta Administración de sesiones para conocer el manejo de transcripciones y los eventos del ciclo de vida, y Controles del lado del servidor si tu servidor necesita su propia conexión de eventos.
Para finalizar la conversación, envía session.close y sigue recibiendo eventos hasta que llegue session.closed, antes de cerrar la conexión entre pares y las pistas del micrófono. El ejemplo registra el escuchador del evento final antes de enviar el comando. Si la conexión falla o se agota el tiempo de espera antes de eso, el uso final queda sin confirmar. Consulta Uso y cierre ordenado para conocer el manejo del uso final.
WebRTC es un potente conjunto de interfaces estándar para crear aplicaciones en tiempo real. La Realtime API de OpenAI permite conectarse a modelos en tiempo real mediante una conexión entre pares de WebRTC.
Para aplicaciones de voz a voz que se ejecutan en el navegador, recomendamos comenzar con Agentes de voz, donde se describen las funciones auxiliares y las API de más alto nivel del SDK de agentes para administrar sesiones en tiempo real. La interfaz de WebRTC es potente y flexible, pero es de más bajo nivel que el SDK de agentes.
Al conectarte a un modelo Realtime desde el cliente (como un navegador web o un dispositivo móvil), recomendamos usar WebRTC en lugar de WebSockets para obtener un rendimiento más estable.
Para obtener más orientación sobre cómo crear interfaces de usuario con WebRTC, consulta la documentación de MDN.
Descripción general
La Realtime API admite dos mecanismos para conectarse a ella desde el navegador: mediante claves de API efímeras (generadas a través de la API REST de OpenAI) o mediante la nueva interfaz unificada. En general, usar la interfaz unificada es más sencillo, pero sitúa al servidor de tu aplicación en la ruta crítica de la inicialización de sesiones.
Conectarse mediante la interfaz unificada
El proceso para inicializar una conexión WebRTC mediante la interfaz unificada es el siguiente (suponiendo que el cliente sea un navegador web):
- El navegador realiza una solicitud a un servidor controlado por el desarrollador usando los datos SDP de su conexión entre pares de WebRTC.
- El servidor combina esos datos SDP con su configuración de sesión en un formulario multiparte y lo envía a la Realtime API de OpenAI, autenticando la solicitud con su clave de API estándar.
Crear una sesión mediante la interfaz unificada
Para crear una sesión de la Realtime API mediante la interfaz unificada, tendrás que crear una pequeña aplicación del lado del servidor (o integrarla con una existente) para realizar una solicitud a /v1/realtime/calls. Usarás una clave de API estándar para autenticar esta solicitud en tu servidor de backend.
A continuación se muestra un ejemplo de un servidor sencillo de Node.js con express que crea una sesión de la Realtime API:
import express from "express";
const app = express();
// Parse raw SDP payloads posted from the browser
app.use(express.text({ type: ["application/sdp", "text/plain"] }));
const sessionConfig = JSON.stringify({
type: "realtime",
model: "gpt-realtime-2.1",
audio: { output: { voice: "marin" } },
});
// An endpoint which creates a Realtime API session.
app.post("/session", async (req, res) => {
const fd = new FormData();
fd.set("sdp", req.body);
fd.set("session", sessionConfig);
try {
const r = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: fd,
});
// Send back the SDP we received from the OpenAI REST API
const sdp = await r.text();
res.send(sdp);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);Si tu aplicación asigna un identificador de seguridad
a cada usuario final, inclúyelo como encabezado OpenAI-Safety-Identifier en esta
solicitud del lado del servidor. Usa un valor estable que preserve la privacidad, como un hash
del ID interno del usuario. Tu backend de confianza debe establecer el encabezado, no el
navegador.
Conectarse al servidor
En el navegador, puedes usar las API estándar de WebRTC para conectarte a la Realtime API a través del servidor de tu aplicación. El cliente envía sus datos SDP directamente a tu servidor mediante una solicitud POST.
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("/session", {
method: "POST",
body: offer.sdp,
headers: {
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);Conectarse mediante un token efímero
El proceso para inicializar una conexión WebRTC con una clave de API efímera es el siguiente (suponiendo que el cliente sea un navegador web):
- El navegador envía una solicitud a un servidor controlado por el desarrollador para generar una clave de API efímera.
- El servidor del desarrollador usa una clave de API estándar para solicitar una clave efímera a la API REST de OpenAI y devuelve esa nueva clave al navegador.
- El navegador usa la clave efímera para autenticar una sesión directamente con la Realtime API de OpenAI como una conexión entre pares de WebRTC.
Crear un token efímero
Para crear un token efímero que se use del lado del cliente, deberás desarrollar una pequeña aplicación del lado del servidor (o integrar esta funcionalidad en una existente) para solicitar una clave efímera a la API REST de OpenAI. Usarás una clave de API estándar para autenticar esta solicitud en tu servidor de backend.
A continuación se muestra un ejemplo de un servidor sencillo de Node.js con express que genera una clave de API efímera mediante la API REST:
import express from "express";
const app = express();
const sessionConfig = JSON.stringify({
session: {
type: "realtime",
model: "gpt-realtime-2.1",
audio: {
output: {
voice: "marin",
},
},
},
});
// An endpoint which would work with the client code above - it returns
// the contents of a REST API request to this protected endpoint
app.get("/token", async (req, res) => {
try {
const response = await fetch(
"https://api.openai.com/v1/realtime/client_secrets",
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: sessionConfig,
}
);
const data = await response.json();
res.json(data);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);Puedes crear un punto de acceso de servidor como este en cualquier plataforma que pueda enviar y recibir solicitudes HTTP. Solo asegúrate de usar las claves de API estándar de OpenAI únicamente en el servidor, no en el navegador.
Cuando uses tokens efímeros, establece OpenAI-Safety-Identifier en la solicitud del lado del servidor
que crea el secreto del cliente. La Realtime API vincula el identificador con
el token efímero resultante, por lo que el navegador no necesita enviar el identificador de seguridad
cuando se conecte posteriormente con ese token.
Conectarse al servidor
En el navegador, puedes usar las API estándar de WebRTC para conectarte a la Realtime API con un token efímero. El cliente primero obtiene un token del punto de acceso de tu servidor y luego envía sus datos SDP (con el token efímero) a la Realtime API mediante una solicitud POST.
// Get a session token for OpenAI Realtime API
const tokenResponse = await fetch("/token");
const data = await tokenResponse.json();
const EPHEMERAL_KEY = data.value;
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
body: offer.sdp,
headers: {
Authorization: `Bearer ${EPHEMERAL_KEY}`,
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);Enviar y recibir eventos
Las sesiones de la Realtime API se administran mediante una combinación de eventos enviados por el cliente que tú emites como desarrollador y eventos enviados por el servidor que la Realtime API crea para indicar eventos del ciclo de vida de la sesión.
Al conectarte a un modelo Realtime mediante WebRTC, no necesitas manejar los eventos de audio del modelo con el mismo nivel de detalle que con WebSockets. El objeto de conexión entre pares de WebRTC, si se configura como se indicó antes, hará todo ese trabajo por ti.
Para enviar y recibir otros eventos del cliente y del servidor, puedes usar el canal de datos de la conexión entre pares de WebRTC.
// This is the data channel set up in the browser code above...
const dc = pc.createDataChannel("oai-events");
// Listen for server events
dc.addEventListener("message", (e) => {
const event = JSON.parse(e.data);
console.log(event);
});
// Send client events
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "hello there!",
},
],
},
};
dc.send(JSON.stringify(event));Para obtener más información sobre cómo administrar conversaciones de Realtime, consulta la guía de conversaciones de Realtime.
Explora la Realtime API con WebRTC en esta aplicación ligera de ejemplo.