DocumentaciónTranscripción en vivo
Transcripción en vivo
Envía audio del micrófono o de una pestaña a transcript1 por WebSocket y recibe líneas de subtítulos, con traducción opcional en tiempo real, mientras la gente habla.
La transcripción en vivo convierte un flujo de audio en líneas de subtítulos mientras la gente todavía habla. Tu cliente envía audio PCM sin comprimir por un WebSocket, y transcript1 devuelve líneas que empiezan como una primera aproximación y luego pasan a ser finales. Agrega translate_to y cada línea incluye también su traducción. Cuando termina la sesión, el resultado se guarda como una transcripción normal.
No hay planes aparte para el modo en vivo: usa el mismo saldo de minutos que todo lo demás. En Facturación verás cómo se cuenta el tiempo conectado.
Cuándo usar la transcripción en vivo
| Usa el modo en vivo cuando | Usa una transcripción normal cuando |
|---|---|
| El audio todavía se está produciendo: una llamada, una charla, una transmisión, un video que estás viendo. | Tienes un enlace o un archivo terminado. |
| Necesitas subtítulos en uno o dos segundos tras la voz. | Quieres la mayor precisión y puedes esperar el resultado. |
| Quieres traducción en tiempo real junto a cada línea. | Quieres traducir una transcripción ya terminada, o a varios idiomas. |
El modo en vivo solo acepta audio, como PCM sin comprimir. No admite enlaces de video, archivos subidos, WebM, Opus ni MP3. Captura el audio en tu cliente y conviértelo antes a PCM. En Captura de audio en el navegador se muestra cómo.
Inicio rápido con el SDK
Instala el SDK 1.5.0 o posterior: npm i @transcript1/sdk@latest. t1.live.start crea la sesión, se conecta y se resuelve cuando la sesión ya empezó, así que puedes enviar audio de inmediato.
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';
import { Transcript1 } from '@transcript1/sdk';
const t1 = new Transcript1(); // reads TRANSCRIPT1_API_KEY
const live = await t1.live.start({ language: 'en', translateTo: 'es', sampleRate: 16000 });
live.on('line', line => {
const mark = line.final ? 'final ' : 'partial';
console.log(mark, line.text, '→', line.translation?.text ?? '');
});
live.on('warning', warning => console.warn(warning.code));
// speech.pcm: 16 kHz, mono, signed 16-bit little-endian, no header
for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
live.send(chunk);
await sleep(100); // 3,200 bytes are 100 ms of audio: send at real-time speed
}
const result = await live.end();
console.log(result.reason, result.transcriptionId, result.minutesCharged);Las líneas se actualizan en su lugar. Reemplaza la línea que ya tienes cuando llegue otra con el mismo id, o lee todas desde live.lines. end() termina la sesión, espera las últimas líneas y guarda la transcripción. Consulta todos los métodos y eventos en SDK para Node.js.
Inicio rápido con un WebSocket directo
Sin el SDK hay dos pasos: crear una sesión por HTTPS y abrir el WebSocket con el token de un solo uso que recibes.
Crea una sesión
curl -X POST https://api.transcript1.com/v1/live/sessions \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
-H "Content-Type: application/json" \
-d '{"language": "en", "translate_to": "es", "sample_rate": 16000}'La API responde 201:
{
"id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
"status": "waiting",
"transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
"websocket_url": "wss://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD/stream",
"token": "Zk3x...",
"token_expires_at": "2026-11-02T12:01:00Z",
"rate": 1
}transcription_id es null cuando save es false. El token es de un solo uso y vence a los 60 segundos, así que conéctate de inmediato. rate es la cantidad de minutos de saldo que se cobra por cada minuto iniciado de esta sesión; consulta Facturación.
El cuerpo de la solicitud acepta estos campos. Todos son opcionales, y el permiso necesario es transcriptions:write:
| Campo | Valor por defecto | Regla |
|---|---|---|
language | auto | auto, o un código de dos letras que admita la transcripción en vivo. |
translate_to | ninguno | Un código de dos letras. Un solo idioma de destino por sesión. Consulta Traducir en vivo. |
diarize | false | Identifica a los hablantes. Algunas configuraciones no pueden hacerlo, por ejemplo junto con language: "auto", y responden 422. Consulta las capacidades. |
audio_format | pcm_s16le | Es el único valor. El campo existe para poder añadir formatos más adelante. |
sample_rate | 16000 | Un entero de 8000 a 48000. Solo mono. |
save | true | Con false no se conserva nada cuando termina la sesión. Consulta Transcripciones guardadas. |
Conéctate y transmite
Abre el WebSocket con el token como segundo subprotocolo. Así funciona en los navegadores y en Node 22+ sin encabezados personalizados. Espera session.started antes de enviar audio: los fragmentos binarios enviados antes se descartan y recibes una advertencia audio_dropped.
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';
const session = await fetch('https://api.transcript1.com/v1/live/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TRANSCRIPT1_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ language: 'en', translate_to: 'es', sample_rate: 16000 }),
}).then(response => response.json());
const ws = new WebSocket(session.websocket_url, ['t1-live', session.token]);
const lines = new Map();
ws.addEventListener('message', async message => {
const event = JSON.parse(message.data);
if (event.type === 'session.started') {
for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
ws.send(chunk);
await sleep(100);
}
ws.send(JSON.stringify({ type: 'end' }));
} else if (event.type === 'line') {
lines.set(event.line.id, event.line); // the full current state: replace by id
console.log(event.line.final, event.line.text, event.line.translation?.text);
} else if (event.type === 'session.ended') {
console.log('ended:', event.reason, event.minutes_charged);
}
});
ws.addEventListener('close', event => console.log('closed', event.code));En Node 20 no hay WebSocket integrado: usa el paquete ws, que tiene la misma forma new WebSocket(url, protocols).
El servidor responde a un token válido con el subprotocolo t1-live y nada más. Un token incorrecto nunca hace fallar el handshake: el socket se abre, recibes un evento error y el socket se cierra con un código 4xxx, de modo que un navegador muestra el motivo real en lugar de un 1006 genérico. Consulta Códigos de cierre.
Captura de audio en el navegador
El audio en vivo debe ser PCM mono de 16 bits con signo, little-endian, a la sample_rate de la sesión. Los navegadores entregan muestras en coma flotante, así que conviértelas en un AudioWorklet. Este fragmento captura el micrófono como PCM de 16 kHz en fragmentos de 100 ms y lo envía con el punto de entrada del SDK para navegador. getAccessToken devuelve un token de acceso OAuth; nunca pongas una clave de API en un navegador. Consulta Seguridad.
import { createLiveClient } from '@transcript1/sdk/live';
const workletSource = `
class Pcm16Processor extends AudioWorkletProcessor {
constructor() {
super();
this.buffer = new Int16Array(1600); // 100 ms at 16 kHz
this.filled = 0;
}
process(inputs) {
const channel = inputs[0][0];
if (!channel) return true;
for (let i = 0; i < channel.length; i++) {
const sample = Math.max(-1, Math.min(1, channel[i]));
this.buffer[this.filled++] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
if (this.filled === this.buffer.length) {
this.port.postMessage(this.buffer.slice());
this.filled = 0;
}
}
return true;
}
}
registerProcessor('pcm16', Pcm16Processor);
`;
const client = createLiveClient({ getAccessToken: () => myApp.getOAuthAccessToken() });
const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } });
const context = new AudioContext({ sampleRate: 16000 });
const moduleUrl = URL.createObjectURL(new Blob([workletSource], { type: 'text/javascript' }));
await context.audioWorklet.addModule(moduleUrl);
// the context's real rate: a browser that cannot do 16 kHz picks its own
const live = await client.start({ language: 'auto', sampleRate: context.sampleRate });
live.on('line', renderLine);
const microphone = context.createMediaStreamSource(stream);
const capture = new AudioWorkletNode(context, 'pcm16');
capture.port.onmessage = event => live.send(event.data); // an Int16Array
const mute = context.createGain();
mute.gain.value = 0;
microphone.connect(capture).connect(mute).connect(context.destination);
async function stop() {
stream.getTracks().forEach(track => track.stop());
await context.close();
return live.end();
}El worklet está escrito para 16 kHz. Si el contexto funciona a otra frecuencia, el fragmento ya no dura 100 ms, pero el PCM sigue siendo correcto porque sampleRate coincide con el del contexto. Para capturar el sonido de una pestaña o de un video en lugar del micrófono, crea el MediaStream con getDisplayMedia o con el captureStream() de un elemento multimedia y conéctalo igual.
Traducir en vivo
Agrega translate_to a la sesión y transcript1 traduce cada línea a medida que se dice.
- Un solo idioma de destino por sesión. Después puedes agregar más idiomas a la transcripción guardada con el endpoint de traducciones.
- Cada
linetiene un original (text,final) y un objetotranslation(language,text,final).translationesnullsi no definistetranslate_to. - El original y la traducción pasan a ser finales por separado: una línea puede tener el original final mientras su traducción todavía cambia. Muestra una línea como final cuando lo sean ambos.
- Una línea que ya está en el idioma de destino usa su propio texto como traducción, y es final cuando la línea se cierra.
- Una línea se cierra cuando el hablante hace una pausa, cambia el hablante, una pausa entre palabras dura más de 0.9 segundos, la línea llega a dos filas de subtítulo (84 caracteres) o a 15 segundos, o envías
finalize. Con la traducción activada, una línea puede tardar unos 3 segundos más en cerrarse mientras llega su traducción. En ese caso la traducción puede seguir abierta (final: false) y seguir creciendo después de que el original ya es final. - La traducción es gratis. No usa minutos.
Los idiomas de destino y los pares de idiomas disponibles dependen del proveedor de voz en vivo. Consulta las capacidades en lugar de fijar reglas en tu código. Un destino no admitido devuelve 422 unsupported_translation. Por ejemplo, un proveedor puede exigir un language explícito en lugar de auto, o permitir solo pares que incluyan inglés.
Protocolo WebSocket
Conexión
new WebSocket(websocket_url, ['t1-live', token]);El token viaja como subprotocolo de WebSocket. Nunca aparece en una URL ni en un registro de acceso. No envíes un encabezado Authorization: el token es la credencial. El servidor envía un ping cada 30 segundos; los navegadores y el paquete ws responden solos. Un socket que no envía pong durante 60 segundos se desconecta.
Una conexión válida a una sesión que ya tiene un socket reemplaza al anterior. Consulta Reanudación y reemplazo.
Mensajes del cliente
| Mensaje | Significado |
|---|---|
| Fragmento binario | Audio: PCM s16le mono a la sample_rate de la sesión. Cada fragmento tiene una longitud par y como máximo 256 KB. Unos 100 ms por fragmento es un buen tamaño. |
{"type":"finalize"} | Cierra ya la línea actual, por ejemplo cuando pausas el video. |
{"type":"end"} | Termina la sesión y la guarda. El servidor responde session.ended y cierra el socket. |
| Cualquier otra cosa | Un evento error no fatal con el código invalid_message. |
Un fragmento de longitud impar o demasiado grande es una violación del protocolo y cierra el socket con 4008.
Empieza a enviar audio después de session.started. Un cliente que envía audio más rápido que el tiempo real paga el tiempo del audio en lugar del tiempo conectado (consulta Facturación), así que envíalo a la velocidad a la que se produce.
Eventos del servidor
Cada evento es un mensaje de texto JSON con un type.
| Evento | Cuándo se envía |
|---|---|
session.started | Se aceptó la conexión. Envía audio a partir de ahora. |
line | Se creó o cambió una línea de subtítulos. |
usage | Después de cada cobro. |
warning | Algo requiere atención, pero la sesión continúa. |
error | Un problema no fatal, o el motivo de un cierre inmediato. |
reconnect | El servidor está por reiniciarse. Reanuda la sesión. |
session.ended | La sesión terminó y se guardó. Es el último evento antes de un cierre normal. |
session.started indica si se trata de una reanudación y cuánto audio ha procesado ya la sesión:
{
"type": "session.started",
"session_id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
"transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
"resumed": false,
"offset_ms": 0
}line trae el estado completo y actual de una línea, así que reemplaza la que tienes con el mismo id. Consulta El objeto de línea.
{
"type": "line",
"line": {
"id": 12,
"start_ms": 53120,
"end_ms": 56040,
"speaker": null,
"language": "en",
"text": "Welcome back to the stream",
"final": true,
"translation": { "language": "es", "text": "Bienvenidos de nuevo", "final": false }
}
}usage se envía después de cada cobro, con el tiempo conectado, el tiempo del audio, los minutos de saldo cobrados hasta ahora y los minutos que te quedan:
{
"type": "usage",
"connected_ms": 61240,
"audio_ms": 60100,
"minutes_charged": 2,
"minutes_remaining": 898
}warning incluye un code:
code | Significado |
|---|---|
balance_low | El próximo cobro fallaría. La sesión termina con balance_exhausted cuando llegue ese momento. No se envía mientras la recarga automática está activada. |
audio_dropped | Llegó audio antes de que la sesión estuviera lista, o demasiado audio mientras se conectaba el proveedor de voz. Se descartó el audio más antiguo. |
idle_timeout_soon | Llevas 4 minutos sin audio. La sesión termina tras 5 minutos sin audio. Envía audio para conservarla. |
{ "type": "warning", "code": "idle_timeout_soon" }error incluye un code y un message. Estos códigos pueden llegar por un socket en vivo:
code | Fatal | Significado |
|---|---|---|
invalid_message | No | Un mensaje de texto que no es finalize ni end. |
invalid_token | Sí | El token no es válido, venció o ya se usó. El socket se cierra con 4001. |
not_found | Sí | La sesión no existe. El socket se cierra con 4004. |
session_ended | Sí | La sesión ya terminó. El socket se cierra con 4004. |
{ "type": "error", "code": "invalid_message", "message": "Expected {\"type\":\"finalize\"} or {\"type\":\"end\"}" }reconnect llega antes de que el servidor cierre el socket con 1012, por ejemplo durante un despliegue. Obtén un token nuevo y reanuda.
{ "type": "reconnect", "reason": "server_restart" }session.ended es el último evento de toda sesión. Las cifras son los totales finales:
{
"type": "session.ended",
"reason": "client_ended",
"transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
"minutes_charged": 3,
"connected_ms": 142500,
"audio_ms": 141900
}El objeto de línea
| Campo | Significado |
|---|---|
id | Un número que empieza en 0 y crece de uno en uno con cada línea. Es el índice del segmento en la transcripción guardada. |
start_ms, end_ms | Inicio y fin en tiempo de audio de la sesión. Este tiempo continúa tras las reconexiones. |
speaker | La etiqueta del hablante cuando diarize está activado; si no, null. Las etiquetas son las mismas que en las transcripciones normales. |
language | El idioma detectado o indicado de la línea, o null si aún no se conoce. |
text | El texto original hasta ahora. |
final | true cuando el texto original ya no cambiará. |
translation | null sin translate_to; en caso contrario, language, text y final. |
Muestra las líneas con final: false con un estilo más tenue: su texto todavía puede cambiar. El texto de una línea con final: true no vuelve a cambiar, y una línea final nunca se elimina.
El SDK usa camelCase para los campos de línea: startMs, endMs.
Motivos de finalización
session.ended y GET /v1/live/sessions/:id devuelven un end_reason:
| Motivo | Significado |
|---|---|
client_ended | Enviaste end. |
balance_exhausted | Falló el siguiente cobro. La sesión termina en un límite de minuto y se guarda. |
idle_timeout | Pasaron 5 minutos sin audio. |
max_duration | La sesión estuvo conectada 300 minutos. Inicia una nueva. |
disconnected | El cliente estuvo ausente más de 2 minutos sin reanudar. |
upstream_error | Falló el proveedor de voz. |
internal_error | Falló algo en transcript1. |
not_connected | La sesión nunca se conectó en 5 minutos. Nunca se cobra y solo se ve mediante GET. |
Códigos de cierre
| Código | Significado |
|---|---|
1000 | Cierre normal. Siempre va después de session.ended. |
1011 | Error inesperado del servidor. El servidor recupera la sesión: reanuda con un token nuevo. |
1012 | Reinicio del servidor. Va después de reconnect: reanuda con un token nuevo. |
4001 | El token no es válido, venció o ya se usó. |
4004 | No se encontró la sesión o ya terminó. |
4008 | Violación del protocolo: un fragmento binario de longitud impar o demasiado grande. |
4010 | Reemplazada por una conexión más nueva a la misma sesión. |
Mientras la API se reinicia, una nueva solicitud de WebSocket puede recibir HTTP 503. Vuelve a intentarlo en un momento.
Reanudación y reemplazo
Una sesión sobrevive a una caída de la red, a un despliegue de la API y a un fallo. Durante 2 minutos después de una desconexión te espera; pasado ese tiempo termina como disconnected y se guarda.
- Llama a
POST /v1/live/sessions/:id/token. La respuesta traetoken,token_expires_atywebsocket_url, los mismos campos que al crear la sesión. Falla con409 session_endedsi la sesión ya terminó. - Abre un WebSocket nuevo con ese token.
- Llega
session.startedconresumed: true.offset_mses la cantidad de audio que la sesión ya procesó: continúa con el audio desde ese punto. Los ids de línea continúan.
Las líneas que ya eran finales se conservan. Se pierde el tramo en curso en el momento de la caída, así que reenvía unos segundos de audio desde offset_ms si lo tienes en un búfer. Los cortes de reconexión no se cobran.
El reemplazo se hace con la misma llamada. Si el servidor aún cree que hay un socket abierto, una conexión válida nueva lo reemplaza y el socket anterior se cierra con 4010 después de guardar sus líneas. El límite de sesiones simultáneas nunca bloquea una reanudación.
El SDK hace todo esto por ti: ante una caída o un evento reconnect, obtiene un token y se reconecta con una espera de 0.5 a 8 segundos durante un máximo de 2 minutos, y reenvía los últimos 10 segundos de audio enviados durante el corte.
Facturación
La transcripción en vivo usa el mismo saldo de minutos que las transcripciones por lotes.
- Se factura el tiempo conectado, desde que se acepta el socket hasta que se cierra. El silencio y un video en pausa cuentan. El tiempo entre una caída y la reanudación no cuenta.
- Por minuto iniciado, con un mínimo de 1. El primer minuto se cobra cuando se acepta la primera conexión, y cada minuto siguiente cuando empieza.
- Más rápido que el tiempo real se factura como tiempo de audio. Si el audio que envías dura más que el tiempo conectado, se factura el tiempo del audio.
- La tarifa se consulta en la API. Un minuto iniciado cuesta
rateminutos de saldo, redondeados hacia arriba. La tarifa suele ser 1 y puede tener decimales. Consulta la tarifa en vivo enrates.livedeGET /v1/account, enratedeGET /v1/live/capabilitiesy enratede la sesión que creas. La tarifa de una sesión queda fija una vez creada. - La traducción es gratis.
- Si nunca se conecta, no se cobra. Una sesión que no se conecta en 5 minutos termina como
not_connected. - Los fallos se reembolsan. Si una sesión termina con
upstream_errorointernal_errorantes de producir una sola línea, se devuelve todo lo cobrado. Una sesión vacía por falta de voz se cobra y se guarda vacía. - Cuando se acaba el saldo y la recarga automática está activada, transcript1 cobra a tu tarjeta guardada mientras la sesión sigue transmitiendo, y la sesión continúa. Si la recarga automática está desactivada o en pausa, o el cobro falla, la sesión se finaliza, se guarda y termina con
balance_exhausteden un límite de minuto. Antes llega la advertenciabalance_low, salvo que la recarga automática esté activada.
Crear una sesión devuelve 402 insufficient_minutes cuando tu saldo utilizable es menor que un minuto iniciado a la tarifa actual. Si la recarga automática está activada, transcript1 intenta recargar primero. Consulta Facturación y límites.
Límites
| Free | Monthly | Annual | |
|---|---|---|---|
| Sesiones en vivo simultáneas | 1 | 2 | 3 |
- Una sesión cuenta mientras está en
waiting,connectedodisconnected(dentro de su ventana de 2 minutos). Si superas el límite,POST /v1/live/sessionsdevuelve429 too_many_live_sessionsconRetry-After: 30. - Las sesiones en vivo no cuentan para el límite de transcripciones simultáneas.
- Una sesión dura como máximo 300 minutos de tiempo conectado. Después termina con
max_duration. - Una sesión sin audio durante 5 minutos termina con
idle_timeout; recibesidle_timeout_soona los 4 minutos. Una transmisión en pausa sigue facturando tiempo conectado y cuenta como inactiva, así que termina la sesión si la pausa es larga. - transcript1 limita todas las sesiones en vivo en conjunto. Cuando están todas en uso,
POST /v1/live/sessionsdevuelve503 live_capacity, que se puede reintentar. EsperaRetry-After(30 segundos) y vuelve a intentarlo. Esto solo se comprueba al crear una sesión, nunca en mitad de una. - Crear sesiones y obtener tokens cuentan para tus requests por minuto.
- El audio es solo PCM sin comprimir:
pcm_s16le, mono, de 8000 a 48000 Hz.
Capacidades
GET /v1/live/capabilities describe qué admite ahora la transcripción en vivo, para que los clientes nunca fijen reglas del proveedor en su código. El permiso necesario es transcriptions:read.
{
"available": true,
"auto_language": true,
"languages": ["en", "es"],
"translation": {
"targets": ["es", "de"],
"pairs": "any",
"requires_language": false
},
"diarize": true,
"audio_formats": ["pcm_s16le"],
"sample_rate": { "min": 8000, "max": 48000 },
"max_session_minutes": 300,
"rate": 1
}Aquí las listas están abreviadas. Léelas desde la API.
translation.pairses"any"(cualquier idioma detectado hacia cualquier destino), o una lista de objetos confromyto.translation.requires_languageestruecuando la traducción necesita unlanguageexplícito en lugar deauto.- Cuando la transcripción en vivo no está configurada, la respuesta es
available: falsecon solorate, y crear una sesión devuelve503 live_unavailable.
Con el SDK: await t1.live.capabilities().
Consultar una sesión
GET /v1/live/sessions/:id devuelve el estado de una sesión. El permiso necesario es transcriptions:read.
curl https://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"{
"id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
"status": "ended",
"end_reason": "client_ended",
"transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
"options": {
"language": "en",
"translate_to": "es",
"diarize": false,
"audio_format": "pcm_s16le",
"sample_rate": 16000,
"save": true
},
"connected_ms": 142500,
"audio_ms": 141900,
"minutes_charged": 3,
"rate": 1,
"created_at": "2026-11-02T11:58:12Z",
"ended_at": "2026-11-02T12:00:35Z"
}status es waiting, connected, disconnected o ended. end_reason es null hasta que la sesión termina. Con el SDK: await t1.live.get(id).
Errores
Los errores de solicitud usan el formato de error habitual. Estos son los que devuelven POST /v1/live/sessions y sus endpoints hermanos:
| Estado HTTP | code | ¿Reintentar? | Cuándo |
|---|---|---|---|
| 422 | invalid_request | No | Falta un campo o no es válido, por ejemplo un sample_rate fuera del rango de 8000 a 48000. |
| 422 | unsupported_language | No | language no está admitido para la transcripción en vivo. |
| 422 | unsupported_translation | No | El idioma de destino, o el par de idiomas, no está admitido. |
| 402 | insufficient_minutes | No | Tu saldo utilizable es menor que un minuto iniciado a la tarifa actual. |
| 403 | email_not_verified | No | Verifica primero tu correo. |
| 429 | too_many_live_sessions | Sí | Ya usas todas las sesiones en vivo simultáneas de tu plan. Espera Retry-After. |
| 503 | live_capacity | Sí | Toda la capacidad en vivo está ocupada. Espera Retry-After (30 segundos). |
| 503 | live_unavailable | No | La transcripción en vivo no está disponible por ahora. |
| 409 | session_ended | No | POST /v1/live/sessions/:id/token para una sesión que ya terminó. |
Los problemas en el socket llegan como eventos error y códigos de cierre.
Transcripciones guardadas
Con save: true (el valor por defecto), la sesión se guarda como una transcripción normal cuando termina. El audio nunca se almacena.
transcription_idse devuelve al crear la sesión. Su estado eslivemientras la sesión está activa ycompletedcuando termina. Una sesión que nunca se conectó deja una transcripcióncanceled.- La transcripción tiene
source: { "platform": "live", "session_id": "lv_…" }. No hay enlace ni archivo. El panel la muestra como sesión en vivo. - Cada línea se convierte en un segmento, con el mismo índice que el
idde la línea. Lee la transcripción como JSON, texto, SRT o VTT igual que cualquier otra: consulta la referencia de la API. - La traducción en vivo se guarda con ella. Léela con el parámetro
translation, como se explica en Traducción. - Más adelante puedes agregar idiomas de traducción con
POST /v1/transcriptions/:id/translations, cuando la transcripción esté encompleted. Consulta Traducción.
Con save: false no se conserva nada cuando termina la sesión: no hay transcripción y transcription_id es null. Igualmente recibes todas las líneas por el socket y se aplica el cobro habitual.
Seguridad
- El token que recibes al crear una sesión es de un solo uso y vence a los 60 segundos. Se envía como subprotocolo de WebSocket, no en la URL, así que no queda en los registros de acceso. Un token filtrado no sirve una vez usado o vencido.
- Usa una clave de API solo desde un servidor que controles. Nunca pongas una clave de API en una página web ni en una extensión.
- En un navegador o una extensión, inicia sesión de las personas con OAuth y pasa el token de acceso OAuth a
getAccessTokendecreateLiveClient. El token necesita el permisotranscriptions:writepara iniciar sesiones. Los tokens de acceso OAuth vencen a la hora, y cada reanudación pide uno nuevo agetAccessToken. - Tu servidor también puede crear la sesión con su clave de API y entregar al navegador solo
websocket_urlytoken. Entonces el cliente se conecta directamente, pero no puede reanudar sin un token nuevo que le des tú.
Última actualización 3 de noviembre de 2026