> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversación en tiempo real

> Establece una conexión WebSocket persistente para una interacción de voz y texto bidireccional de baja latencia con un modelo de conversación

## Introducción

La conversación en tiempo real establece una conexión persistente mediante WebSocket (un protocolo que mantiene una conexión duradera y bidireccional entre el cliente y el servidor), envía tu audio o texto al modelo de conversación en tiempo real y el modelo devuelve texto y voz de forma incremental. Es adecuada para asistentes de voz, preguntas y respuestas en tiempo real, práctica oral y otros escenarios que requieren interacción de ida y vuelta.

Igual que la [transcripción en tiempo real](/es/api/realtime-transcription), funciona sobre WebSocket, pero sus usos son distintos:

| Dimensión                           | Transcripción en tiempo real                      | Conversación en tiempo real (esta página)                                        |
| ----------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------- |
| Objetivo                            | Convertir voz en texto                            | Mantener una conversación de varios turnos en la que el modelo genera respuestas |
| Dirección                           | Unidireccional: audio de entrada, texto de salida | Bidireccional: audio o texto de entrada, texto y voz de salida                   |
| Parámetros de conexión              | `?intent=transcription&model=...`                 | Solo `?model=...` (sin `intent`)                                                 |
| Detección de actividad de voz (VAD) | No compatible, debe ser `null`                    | Compatible, detección automática de turnos disponible                            |
| Casos de uso típicos                | Subtítulos de reuniones, dictado en directo       | Asistentes de voz, interacción oral en tiempo real                               |

**Modelo disponible:**

* **gpt-realtime-2.1**: modelo de conversación por voz, admite entrada de audio y texto y produce respuestas de texto y voz en tiempo real.

<Warning>
  **Esta API está pensada para integración del lado del servidor; los navegadores no pueden conectarse directamente.** Por motivos de seguridad, la pasarela valida y rechaza las conexiones que llevan una cabecera `Origin`, rechaza el subprotocolo `openai-insecure-api-key` y solo acepta la clave mediante la cabecera estándar `Authorization`. Un WebSocket iniciado por el navegador añade automáticamente una cabecera `Origin` y, por tanto, se rechaza. Si necesitas conversación en tiempo real en el frontend, establece la conexión a la pasarela desde tu propio servidor y reenvía el audio y los resultados entre el frontend y el servidor.
</Warning>

## Inicio rápido

### Punto de conexión

```text theme={null}
wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1
```

* `model=gpt-realtime-2.1`: **obligatorio**, el modelo queda fijado al conectar mediante el parámetro de la URL y no puede cambiarse durante la sesión (consulta las restricciones más abajo).
* **Atención a la diferencia con la transcripción**: el punto de conexión de conversación **no** lleva `intent=transcription`.

### Autenticación

Envía la clave en una cabecera HTTP estándar durante el handshake:

```text theme={null}
Authorization: Bearer $AIHUBMIX_API_KEY
```

### Requisitos de formato de audio

El audio de entrada y de salida solo admite actualmente un formato. Convierte tu audio antes de enviarlo:

* **Codificación**: PCM16 (entero con signo de 16 bits, little-endian)
* **Frecuencia de muestreo**: 24000 Hz
* **Canales**: mono

Es decir, `audio/pcm@24000`. Declarar otro formato (como G.711/µ-law) en la entrada o la salida se rechaza y cierra la sesión.

<Note>
  A diferencia de la transcripción, las sesiones de conversación **sí admiten** la detección de actividad de voz (turn\_detection / VAD). Al activarla, el modelo determina automáticamente cuándo termina una intervención y desencadena una respuesta; al desactivarla (valor `null`), controlas tú cuándo confirmar el audio y cuándo solicitar una respuesta. Elige según lo que necesites.
</Note>

## Configuración de sesión (session.update)

Una vez establecida la conexión, el cliente puede enviar una trama `session.update` para configurar parámetros de la conversación (voz, instrucciones del sistema, si se activa el VAD). El modelo de la sesión de conversación ya está anclado por la URL de conexión, así que **puedes conversar sin enviar `session.update`**; envíala cuando necesites una voz o instrucciones personalizadas.

```json theme={null}
{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "instructions": "You are a helpful voice assistant. Keep answers concise.",
    "audio": {
      "input": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "turn_detection": { "type": "server_vad" }
      },
      "output": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "voice": "alloy"
      }
    }
  }
}
```

### Parámetros de configuración

<ParamField body="session.type" type="string" required>
  Tipo de sesión, `realtime` para el escenario de conversación.
</ParamField>

<ParamField body="session.instructions" type="string">
  Instrucciones del sistema que definen el rol, el tono y las restricciones de respuesta del modelo.
</ParamField>

<ParamField body="session.output_modalities" type="string[]">
  Modalidades de salida, `["audio"]` o `["text"]`: con `["audio"]` (predeterminado) el modelo genera voz y el texto de la respuesta llega por el evento `response.output_audio_transcript.delta`; con `["text"]` solo genera texto, que se entrega por `response.output_text.delta`. Cualquier otra combinación (por ejemplo `["audio", "text"]`) se rechaza y devuelve un evento `error`.
</ParamField>

<ParamField body="session.audio.input.format" type="object" required>
  Formato de audio de entrada, fijado en `{ "type": "audio/pcm", "rate": 24000 }`.
</ParamField>

<ParamField body="session.audio.input.turn_detection" type="object | null">
  Detección de actividad de voz. Pasa `{ "type": "server_vad" }` para activar la detección automática de turnos; pasa `null` para desactivarla y confirmar el audio y solicitar respuestas manualmente desde el cliente.
</ParamField>

<ParamField body="session.audio.output.format" type="object" required>
  Formato de audio de salida, fijado en `{ "type": "audio/pcm", "rate": 24000 }`.
</ParamField>

<ParamField body="session.audio.output.voice" type="string">
  Voz de la respuesta de audio. **No puede cambiarse una vez iniciada la primera respuesta**: cuando la sesión entra en estado de generación, un `voice` enviado de nuevo se ignora (el resto de ajustes sí se aplican). Por tanto, si necesitas una voz concreta, defínela antes de solicitar la primera respuesta.
</ParamField>

<Note>
  Las siguientes capacidades **no son compatibles en esta versión** y cierran la sesión al configurarlas (código de cierre `1008`): activar la transcripción integrada dentro de una sesión de conversación (`audio.input.transcription`, motivo `input_transcription_not_supported`), inyectar audio (motivo `item_audio_not_supported`) o imágenes (motivo `image_input_not_supported`) mediante elementos de conversación, y cualquier tipo de elemento de contenido distinto de texto (motivo `unsupported_content_part`). Envía todo el audio por el canal `input_audio_buffer.append`.
</Note>

## Envío de entrada

### Envío de audio

Divide el audio PCM16 en fragmentos pequeños (por ejemplo, uno cada 100 ms), codifícalos en base64 y envíalos de forma continua con el evento `input_audio_buffer.append`:

```json theme={null}
{
  "type": "input_audio_buffer.append",
  "audio": "<fragmento de audio PCM16 codificado en base64>"
}
```

Con el VAD activado, el modelo detecta el final de una intervención y desencadena una respuesta automáticamente. Con el VAD desactivado, tras enviar un segmento de audio debes confirmar y solicitar una respuesta manualmente:

```json theme={null}
{ "type": "input_audio_buffer.commit" }
{ "type": "response.create" }
```

### Envío de texto

También puedes inyectar directamente un mensaje de texto y solicitar una respuesta:

```json theme={null}
{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [{ "type": "input_text", "text": "Introduce yourself in one sentence." }]
  }
}
{ "type": "response.create" }
```

## Recepción de respuestas

El servidor sigue enviando eventos. Tipos de evento clave:

<ParamField body="session.created / session.updated" type="event">
  Confirmación de que la sesión se creó o de que se actualizó su configuración. Puedes empezar a enviar audio y texto al recibir `session.created`.
</ParamField>

<ParamField body="conversation.item.added / conversation.item.done" type="event">
  Un elemento de conversación se ha escrito: cada entrada del usuario y cada respuesta del modelo añaden un elemento.
</ParamField>

<ParamField body="input_audio_buffer.speech_started / input_audio_buffer.speech_stopped" type="event">
  Con la VAD activada, el servidor ha detectado que el usuario empieza o deja de hablar. Un evento `speech_started` suele significar que el usuario está interrumpiendo al modelo; consulta [Interrupción y truncado](#interruption).
</ParamField>

<ParamField body="response.created" type="event">
  Una respuesta ha empezado a generarse.
</ParamField>

<ParamField body="response.output_item.added / response.output_item.done" type="event">
  El elemento de salida de la respuesta ha empezado y ha terminado. El campo `item.id` del evento `added` es el ID del elemento de conversación que debes referenciar al truncar el audio más adelante.
</ParamField>

<ParamField body="response.output_audio.delta / response.output_audio.done" type="event">
  Un fragmento **incremental** del audio de la respuesta (PCM16 codificado en base64) y su marca de fin, que puedes reproducir a medida que llega.
</ParamField>

<ParamField body="response.output_audio_transcript.delta / response.output_audio_transcript.done" type="event">
  La transcripción **incremental** que corresponde al audio de la respuesta frase por frase, y su marca de fin; el campo `delta` contiene el texto añadido. **Cuando la salida incluye audio, toma el texto de la respuesta de este evento**, por ejemplo para mostrar subtítulos mientras se reproduce el audio.
</ParamField>

<ParamField body="response.output_text.delta / response.output_text.done" type="event">
  Un fragmento **incremental** de una respuesta solo de texto y su marca de fin, emitidos únicamente cuando la modalidad de salida es solo texto (`output_modalities: ["text"]`).
</ParamField>

<ParamField body="response.done" type="event">
  Una respuesta ha terminado. Este evento incluye el uso de tokens del turno (`usage`), que es la base de la facturación.
</ParamField>

<ParamField body="conversation.item.truncated" type="event">
  Confirmación de que la solicitud de truncado ha surtido efecto; consulta [Interrupción y truncado](#interruption).
</ParamField>

<ParamField body="error" type="event">
  Evento de error, con código y descripción. Un problema de la propia solicitud (por ejemplo un valor `output_modalities` no válido) devuelve un único evento `error` y la sesión sigue siendo utilizable; las cuestiones de política (cambio de modelo, saldo agotado) cierran la sesión.
</ParamField>

<Note>
  **Elige bien el evento para el texto de la respuesta.** Por defecto (salida con audio) el modelo solo envía `response.output_audio_transcript.delta` y **no** envía `response.output_text.delta`; si defines la modalidad de salida como solo texto, el texto pasa a `response.output_text.delta`. Escucha ambos en cualquiera de los dos modos para no perder texto (consulta la [salida medida](#run-output) más abajo).
</Note>

<h2 id="interruption">
  Interrupción y truncado
</h2>

Cuando el usuario empieza a hablar mientras el modelo habla, el contenido ya generado pero aún no reproducido entra en conflicto con la siguiente frase del usuario. En una conexión WebSocket la reproducción la gestiona el cliente, así que el cliente también completa la limpieza tras una interrupción.

Con la VAD activada, el servidor envía `input_audio_buffer.speech_started` en cuanto detecta que el usuario ha empezado a hablar. Al recibir ese evento, el cliente debe:

1. **Detener de inmediato la reproducción local** y anotar hasta dónde se había reproducido la respuesta (en milisegundos).
2. Enviar `conversation.item.truncate` para quitar de la conversación el audio no reproducido, de modo que el modelo no lo trate como dicho en el siguiente turno.

```json theme={null}
{
  "type": "conversation.item.truncate",
  "item_id": "item_ABC123",
  "content_index": 0,
  "audio_end_ms": 1500
}
```

* `item_id`: el ID del elemento de conversación de esta respuesta, tomado de `item.id` en el evento `response.output_item.added`.
* `content_index`: el índice de la parte de contenido de audio, siempre `0`.
* `audio_end_ms`: la longitud de audio que se conserva, en milisegundos, según la posición que el cliente haya reproducido realmente.

El servidor responde con `conversation.item.truncated` cuando la solicitud se ha procesado. El truncado solo afecta al audio de esta respuesta y a su transcripción; la sesión no se ve afectada y puedes continuar con el siguiente turno. Con el SDK de OpenAI, llama a `conn.conversation.item.truncate(item_id=..., content_index=0, audio_end_ms=...)`.

Con la VAD desactivada (por ejemplo, pulsar para hablar), la pulsación es la interrupción: envía `response.cancel` para cancelar la respuesta en curso y trunca como se describe arriba; al soltar, envía `input_audio_buffer.append`, `input_audio_buffer.commit` y `response.create` en ese orden.

## Ejemplos completos

A continuación se muestran tres enfoques; elige uno:

* **SDK oficial de OpenAI (recomendado)**: no necesitas escribir las tramas WebSocket a mano; apunta `websocket_base_url` (el parámetro de URL base de WebSocket del SDK) a la pasarela y reutiliza la biblioteca oficial.
* **SDK oficial de OpenAI Agents**: la forma de voz en tiempo real del framework de agentes oficial; sustituye la `url` de `model_config` por la dirección de la pasarela.
* **websockets sin procesar**: sin SDK, intercambia tramas directamente según el protocolo. Menos dependencias y más fácil de depurar.

<Note>
  **¿Por qué pasar el nombre del modelo al conectar?** La pasarela de AiHubMix necesita el `model` en el momento del handshake WebSocket para seleccionar el proveedor de modelos, autenticar y reservar cupo, mientras que `session.update` llega después de completarse el handshake. Por eso, con un SDK debes pasar `model` explícitamente a `connect()` (el SDK lo inserta en la consulta de la URL); sin él, la pasarela **rechaza durante el handshake** y no se establece ninguna conexión. A diferencia de la transcripción, el punto de conexión de conversación **no** necesita `intent=transcription`.
</Note>

<CodeGroup>
  ```python Python (SDK de OpenAI) theme={null}
  # Dependencia: pip install "openai[realtime]"
  import asyncio
  import base64
  from openai import AsyncOpenAI

  # Reutilizar el SDK oficial solo requiere dos adaptaciones:
  #   1) apuntar websocket_base_url a la pasarela de AiHubMix (no a la dirección predeterminada de OpenAI)
  #   2) pasar model explícitamente a connect(), necesario para el handshake (la conversación no necesita intent)
  client = AsyncOpenAI(
      api_key="sk-***",  # Sustituye por tu clave de API de AiHubMix
      websocket_base_url="wss://aihubmix.com/v1",
  )

  async def main():
      async with client.realtime.connect(model="gpt-realtime-2.1") as conn:
          # 1) Configurar la sesión: instrucciones del sistema + voz + VAD (detección automática de turnos)
          await conn.session.update(session={
              "type": "realtime",
              "instructions": "You are a helpful voice assistant. Keep answers concise.",
              "audio": {
                  "input": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "turn_detection": {"type": "server_vad"},
                  },
                  "output": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "voice": "alloy",
                  },
              },
          })

          # 2) Enviar un mensaje de texto y solicitar una respuesta (para audio, consulta append/commit en el ejemplo sin procesar)
          await conn.conversation.item.create(item={
              "type": "message",
              "role": "user",
              "content": [{"type": "input_text", "text": "What is the capital of France?"}],
          })
          await conn.response.create()

          # 3) Recibir la respuesta de este turno: el texto llega como deltas de transcripción y la voz como deltas de audio
          async for event in conn:
              if event.type == "response.output_audio_transcript.delta":
                  print(event.delta, end="", flush=True)  # texto de la respuesta (salida con audio)
              elif event.type == "response.output_audio_transcript.done":
                  print()  # transcripción de este turno finalizada
              elif event.type == "response.output_text.delta":
                  print(event.delta, end="", flush=True)  # salida solo de texto
              elif event.type == "response.output_text.done":
                  print()
              elif event.type == "response.output_audio.delta":
                  pass  # fragmento de audio PCM16 en base64, decodificar para reproducir
              elif event.type == "input_audio_buffer.speech_started":
                  pass  # interrupción del usuario: detener la reproducción y llamar a conn.conversation.item.truncate(...)
              elif event.type == "response.done":
                  print("\n[turno completado]", event.response.usage)
                  break
              elif event.type == "error":
                  print("\n[error]", event.to_dict())
                  break

  asyncio.run(main())
  ```

  ```python Python (websockets) theme={null}
  import asyncio
  import base64
  import json
  import websockets

  API_KEY = "sk-***"  # Sustituye por tu clave de API de AiHubMix
  URL = "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1"

  async def main():
      # websockets >= 13 usa additional_headers; las versiones anteriores usan extra_headers
      async with websockets.connect(
          URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
      ) as ws:
          # 1) Configurar la sesión (VAD desactivado, control manual del momento de la respuesta)
          await ws.send(json.dumps({
              "type": "session.update",
              "session": {
                  "type": "realtime",
                  "instructions": "You are a helpful voice assistant.",
                  "audio": {
                      "input": {
                          "format": {"type": "audio/pcm", "rate": 24000},
                          "turn_detection": None,
                      },
                      "output": {
                          "format": {"type": "audio/pcm", "rate": 24000},
                          "voice": "alloy",
                      },
                  },
              },
          }))

          # 2) Leer un archivo PCM16 / 24 kHz / mono sin procesar local, enviarlo por fragmentos y luego commit + solicitud
          async def send_audio():
              with open("audio_pcm16_24k.raw", "rb") as f:
                  pcm = f.read()
              chunk = 24000 * 2 // 10  # 100 ms = frecuencia de muestreo x 2 bytes / 10
              for i in range(0, len(pcm), chunk):
                  await ws.send(json.dumps({
                      "type": "input_audio_buffer.append",
                      "audio": base64.b64encode(pcm[i:i + chunk]).decode(),
                  }))
                  await asyncio.sleep(0.1)  # simular el ritmo en tiempo real
              # Con el VAD desactivado, confirmar y solicitar una respuesta manualmente
              await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
              await ws.send(json.dumps({"type": "response.create"}))

          asyncio.create_task(send_audio())

          # 3) Recibir la respuesta
          async for msg in ws:
              evt = json.loads(msg)
              etype = evt.get("type", "")
              if etype == "response.output_audio_transcript.delta":
                  print(evt.get("delta", ""), end="", flush=True)  # texto de la respuesta (salida con audio)
              elif etype == "response.output_audio_transcript.done":
                  print()  # transcripción de este turno finalizada
              elif etype == "response.output_text.delta":
                  print(evt.get("delta", ""), end="", flush=True)  # salida solo de texto
              elif etype == "input_audio_buffer.speech_started":
                  pass  # interrupción del usuario: detener la reproducción y enviar conversation.item.truncate
              elif etype == "response.output_audio.delta":
                  pass  # fragmento de audio PCM16 en base64, decodificar para reproducir
              elif etype == "response.done":
                  print("\n[turno completado]", evt.get("response", {}).get("usage"))
                  break
              elif etype == "error":
                  print("\n[error]", evt.get("error"))
                  break

  asyncio.run(main())
  ```

  ```python Python (SDK de Agents) theme={null}
  # Dependencia: pip install openai-agents
  import asyncio
  from agents.realtime import (
      OpenAIRealtimeWebSocketModel,
      RealtimeAgent,
      RealtimeSession,
  )

  agent = RealtimeAgent(
      name="Assistant",
      instructions="You are a helpful voice assistant. Keep answers concise.",
  )

  async def main():
      session = RealtimeSession(
          OpenAIRealtimeWebSocketModel(),
          agent,
          None,
          model_config={
              "api_key": "sk-***",  # Sustituye por tu clave de API de AiHubMix
              "url": "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1",
          },
          run_config={
              "model_settings": {
                  "modalities": ["audio"],
                  "output_audio_format": "pcm16",
                  # Obligatorio desactivarlo: la transcripción de audio de entrada activada por
                  # defecto no es compatible con esta API, si no la sesión se cierra con 1008
                  "input_audio_transcription": None,
              },
          },
      )
      async with session:
          await session.send_message("What is the capital of France?")
          async for event in session:
              if getattr(event, "type", "") == "raw_model_event":
                  data = getattr(event, "data", None)
                  if getattr(data, "type", "") == "transcript_delta":
                      print(getattr(data, "delta", ""), end="", flush=True)
                  elif getattr(data, "type", "") == "turn_ended":
                      print("\n[turno completado]")
                      return

  asyncio.run(main())
  ```

  ```bash Prueba de conexión (wscat) theme={null}
  # Verifica rápidamente la conectividad y la autenticación con wscat (requiere npm i -g wscat)
  wscat -c "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"

  # Una vez conectado, pega una trama session.update y luego envía un mensaje de texto + response.create para empezar
  ```
</CodeGroup>

<Tip>
  Para convertir cualquier audio al formato PCM sin procesar que exige esta API, usa ffmpeg:

  ```bash theme={null}
  ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio_pcm16_24k.raw
  ```
</Tip>

### Reutilizar ejemplos oficiales

La mayoría de los ejemplos de conversación en tiempo real que publica OpenAI solo dependen del parámetro de URL base del SDK, así que puedes reutilizarlos apuntando la dirección al punto de conexión de AiHubMix:

| Ejemplo oficial                                                                                    | Reutilizable solo cambiando la dirección | Qué hay que cambiar                                                                                                                                                                                                                                              |
| -------------------------------------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ejemplo de conversación en tiempo real del SDK de Python oficial                                   | Sí                                       | Definir `websocket_base_url` en `wss://aihubmix.com/v1` y pasar `model` a `connect()`                                                                                                                                                                            |
| Ejemplo de conversación en tiempo real del SDK de Node / JS oficial                                | Sí                                       | Definir `baseURL` en `https://aihubmix.com/v1` (el SDK lo convierte en `wss` y construye `/realtime?model=...`)                                                                                                                                                  |
| Ejemplo de voz en tiempo real del SDK de Agents oficial                                            | Sí                                       | Definir `model_config.url` en `wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1` y desactivar la transcripción de audio de entrada que activa por defecto                                                                                                   |
| Ejemplos oficiales para navegador (como la consola en tiempo real y el ejemplo de voz multiagente) | No                                       | Estos ejemplos se conectan directamente desde el navegador y son rechazados por la validación de `Origin` de la pasarela; además dependen de credenciales temporales emitidas por un servidor, mientras que esta API solo expone WebSocket del lado del servidor |

<h2 id="run-output">
  Salida medida (en producción)
</h2>

El siguiente es el resultado real del ejemplo del SDK de OpenAI en el entorno de producción `aihubmix.com` con el modelo `gpt-realtime-2.1`. La sesión activa `server_vad`, y tanto los prompts como el audio están en inglés.

**Entrada de texto**

```text theme={null}
# Enviar el texto "What is the capital of France?"
[turno 1, texto] completado en 2,33 s | salida de audio de 2,70 s en PCM16
  respuesta: The capital of France is Paris.
  usage: input_tokens 29 (text 29) / output_tokens 81 (audio 54 + text 27, incluido reasoning 8)
```

**Entrada de audio**

```text theme={null}
# Enviar 6,3 s de voz en inglés por fragmentos; el modelo detecta el final del turno y responde
[turno 2, audio] completado en 9,59 s | salida de audio de 4,35 s en PCM16
  respuesta: Everything sounds good on my side, and I'm ready to keep this conversation going.
  usage: input_tokens 117 (audio 67 + text 50) / output_tokens 129 (audio 87 + text 42, incluido reasoning 11)
Eventos VAD: input_audio_buffer.speech_started / input_audio_buffer.speech_stopped
```

**Diferencia medida entre dos configuraciones**

| Configuración                            | Resultado medido                                                                                                                                                                             |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `turn_detection: {"type": "server_vad"}` | Tras enviar el audio recibes `input_audio_buffer.speech_started` y `speech_stopped`, y el modelo detecta el final del turno y empieza a responder sin `commit` ni `response.create` manuales |
| `output_modalities: ["text"]`            | El texto de la respuesta pasa a `response.output_text.delta`, no hay eventos de salida de audio en el turno y `audio_tokens` es 0 en el usage                                                |

<Note>
  Confirmación medida: por defecto (salida con audio) el texto de la respuesta solo llega carácter a carácter por `response.output_audio_transcript.delta`, y `response.output_text.delta` nunca aparece; `response.done` lleva el `usage` del turno; el handshake tarda entre 2 y 4 segundos, el coste de la reserva de cupo al establecer la sesión.
</Note>

## Facturación

* **Facturación por token**: una sesión de conversación devuelve el uso de tokens de cada turno (`usage`) con el evento `response.done`, y la facturación se basa en él. El uso se mide por separado según el componente, incluidos **entrada de audio, salida de audio, entrada de texto y salida de texto**. El precio unitario de cada componente sigue el precio publicado en vivo en la [página de detalle del modelo](https://aihubmix.com/model/gpt-realtime-2.1).
* **Liquidación al vuelo**: se trata de una conexión de larga duración, y los cargos se descuentan en tiempo real con cada turno durante la sesión, sin una liquidación única al final. Al establecer la sesión se hace primero una **reserva de cupo** de aproximadamente un minuto de uso (solo una comprobación de admisión, no un cargo real), y la reserva restante se libera al terminar la sesión. **Por tanto, el saldo disponible de tu cuenta debe cubrir al menos un minuto de uso para que la sesión pueda establecerse.**
* Cada registro de facturación de conversación en tiempo real puede consultarse uno a uno en [Uso y facturación](https://aihubmix.com).

## Límites y restricciones

1. **Duración de la sesión**: una conexión WebSocket dura como máximo **62 minutos**, tras lo cual el servidor la cierra (código de cierre `1000`, motivo `session_duration_limit`); divide en segmentos si necesitas más tiempo.
2. **Desconexión por inactividad**: cuando ni el cliente ni el modelo tienen actividad durante **5 minutos**, el servidor cierra la sesión (código de cierre `1008`, motivo `idle_timeout`). La actividad de cualquiera de las dos partes reinicia el temporizador, por lo que una respuesta larga que sigue transmitiéndose no se interrumpe.
3. **Saldo insuficiente**: **al establecer la sesión**, si el saldo disponible no cubre la reserva de aproximadamente un minuto, el handshake se rechaza de inmediato (HTTP 403) y no se establece ninguna sesión; **durante la sesión**, si el saldo se agota, la conexión establecida se cierra de inmediato.
4. **Solo del lado del servidor**: no se admiten conexiones directas desde el navegador (se valida la cabecera `Origin`); integra desde tu servidor.
5. **Modelo fijado**: el modelo queda fijado en la URL de conexión, y cambiarlo con `session.update` durante la sesión se rechaza y cierra la sesión.
6. **Formato fijado**: el audio de entrada y de salida solo admite `audio/pcm@24000` mono; otros formatos se rechazan.
7. **Voz fijada**: `voice` no puede cambiarse una vez iniciada la primera respuesta; defínela antes de solicitar la primera respuesta.
8. **No compatible**: la transcripción integrada dentro de una sesión de conversación, la inyección de contenido de audio o imagen mediante elementos de conversación y cualquier tipo de elemento de contenido distinto de texto.
9. **Una respuesta a la vez**: una sesión solo permite una respuesta en curso; enviar otro `response.create` antes de que termine el turno actual se rechaza y cierra la sesión (código de cierre `1008`, motivo `response_already_active`).

## Errores comunes

| Escenario                                                           | Código de cierre / estado                  | Descripción                                                                                              |
| ------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| Servicio no activado                                                | HTTP 403 `realtime_disabled`               | La conversación en tiempo real no está abierta para este entorno                                         |
| Cabecera `Origin` presente / conexión directa desde el navegador    | Handshake rechazado                        | Usa una conexión del lado del servidor                                                                   |
| Saldo insuficiente al conectar                                      | HTTP 403 `insufficient_user_quota`         | El saldo no cubre la reserva de aproximadamente un minuto; recarga y reintenta                           |
| Cambiar el modelo en la sesión                                      | `1008` `model_override_forbidden`          | El modelo solo puede indicarse en la URL de conexión                                                     |
| Formato de audio distinto de PCM                                    | `1008` `audio_format_unsupported`          | Convierte a `audio/pcm@24000`                                                                            |
| Cambiar la voz tras la primera respuesta                            | Campo `voice` ignorado                     | Define `voice` antes de solicitar la primera respuesta                                                   |
| Activar la transcripción integrada                                  | `1008` `input_transcription_not_supported` | No compatible en sesiones de conversación en esta versión                                                |
| Inyectar audio mediante elementos de conversación                   | `1008` `item_audio_not_supported`          | Envía el audio por `input_audio_buffer.append`                                                           |
| Inyectar imágenes mediante elementos de conversación                | `1008` `image_input_not_supported`         | No compatible en esta versión                                                                            |
| Elemento de conversación con un tipo de contenido distinto de texto | `1008` `unsupported_content_part`          | El contenido de los elementos de conversación solo admite texto                                          |
| Valor `output_modalities` no válido                                 | evento `error` `invalid_value`             | Solo se admiten `["audio"]` y `["text"]`; la sesión no se cierra                                         |
| Se alcanza la duración máxima de la sesión                          | `1000` `session_duration_limit`            | Se alcanzó el límite de 62 minutos; vuelve a conectar para continuar                                     |
| Ambas partes inactivas 5 minutos                                    | `1008` `idle_timeout`                      | La actividad de cualquiera de las dos partes reinicia el temporizador                                    |
| Nueva solicitud mientras hay una respuesta en curso                 | `1008` `response_already_active`           | Espera a `response.done` antes de enviar `response.create`                                               |
| `event_id` duplicado                                                | `1008` `duplicate_event_id`                | El `event_id` de cada evento del cliente debe ser único en la sesión                                     |
| `response.conversation` con un valor no predeterminado              | `1008` `conversation_mode_not_supported`   | Solo se admite el modo de conversación predeterminado                                                    |
| El cliente envía un evento exclusivo del servidor                   | `1008` `client_forged_lifecycle_event`     | En el espacio de nombres `response.*` el cliente solo puede enviar `response.create` y `response.cancel` |
| Saldo agotado durante la sesión                                     | Conexión cerrada                           | Recarga y vuelve a conectar                                                                              |

***

Última actualización: 2026-09-21
