Transcripción de audio en tickets
Convierte en texto el audio que graba el usuario, en el momento (hasta 10 MB) o en segundo plano, para dictar tickets por voz. Permiso support:tickets:ai.
Actualizado el . 5 min de lectura
Incluido en los planes Profesional y Avanzado. Ver precios
Con estos endpoints conviertes en texto el audio que graba el usuario, para que pueda dictar la descripción de su ticket o para alimentar las sugerencias con IA. Hay dos modos: en el momento (archivos de hasta 10 MB) y en segundo plano (archivos más grandes).
Todas las peticiones llevan la cabecera X-Api-Key. Igual que las
sugerencias, exigen el aplicativo Tickets de soporte con tickets
propios (support.own_tickets) en el plan y el permiso
support:tickets:ai en la aplicación, que solo concede un Super
Administrador.
Resumen de endpoints
| Método | Ruta | Descripción |
|---|---|---|
POST | /api/external/v1/tickets/ai-transcribe | Transcribir en el momento (hasta 10 MB) |
POST | /api/external/v1/tickets/ai-transcribe-async | Transcribir en segundo plano (archivos grandes) |
GET | /api/external/v1/tickets/ai-transcribe/:jobId/stream | Resultado de la transcripción por SSE |
GET | /api/external/v1/tickets/ai-transcribe/:jobId | Estado del trabajo de transcripción |
Qué endpoint usar
Dos formas de transcribir
Hasta 10 MB
En el momento
POST /ai-transcribe responde con el texto en la misma llamada. Basta para casi todas las notas de voz grabadas en un navegador o en el móvil.Más de 10 MB
En segundo plano
POST /ai-transcribe-async devuelve un jobId. El texto llega después por SSE (GET .../:jobId/stream) o consultando el estado (GET .../:jobId).Transcribir en el momento
/api/external/v1/tickets/ai-transcribeTranscribe el audio y devuelve el texto en la misma respuesta. Archivos de hasta 10 MB. Permiso: support:tickets:ai.
Cabeceras
- X-Api-Keystringobligatorio
- Clave de API externa (formato: utl_xxx)
- Content-Typestringobligatorio
- application/json
- x-request-idstring
- Identificador único para seguir la petición
Cuerpo de la petición
- audioBase64stringobligatorio
- Audio codificado en base64
- audioFilenamestringobligatorio
- Nombre del archivo con extensión, que indica el formato. Por ejemplo, grabacion.webm o nota-voz.mp3
Respuesta correcta (201 Created)
- transcriptionstring
- Texto transcrito del audio
El código es 201, no 200. Si tu cliente comprueba el estado, acepta cualquier 2xx.
Errores
| Código HTTP | Código de error | Descripción |
|---|---|---|
| 400 | AI_AUDIO_TOO_LARGE | El audio supera los 10 MB. Usa el modo en segundo plano |
| 400 | VALIDATION_ERROR | Faltan campos obligatorios o el formato del audio no es válido |
| 401 | -- | Falta la clave de API o no es válida |
Ejemplos
# Codificar el audio en base64 y enviarlo
AUDIO_BASE64=$(base64 -i grabacion.webm)
curl -X POST 'https://os.utilia.ai/api/external/v1/tickets/ai-transcribe' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-transcribe-001' \
-d "{
\"audioBase64\": \"${AUDIO_BASE64}\",
\"audioFilename\": \"grabacion.webm\"
}"Este modo espera a que termine la transcripción antes de responder: con archivos cerca de los 10 MB puede tardar varios segundos, así que muestra un indicador de carga en tu interfaz.
Transcribir en segundo plano
/api/external/v1/tickets/ai-transcribe-asyncEmpieza una transcripción en segundo plano, para audios de cualquier tamaño. Permiso: support:tickets:ai.
Cabeceras
- X-Api-Keystringobligatorio
- Clave de API externa (formato: utl_xxx)
- Content-Typestringobligatorio
- application/json
- x-request-idstring
- Identificador único para seguir la petición
Cuerpo de la petición
- audioBase64stringobligatorio
- Audio codificado en base64
- audioFilenamestringobligatorio
- Nombre del archivo con extensión (por ejemplo, reunion-larga.mp3)
Respuesta correcta (201 Created)
- jobIdstring
- Identificador del trabajo de transcripción. Úsalo para consultar el resultado
Aunque el trabajo queda en cola, el código es 201, no 202. Acepta cualquier 2xx y quédate con el jobId del cuerpo.
Ejemplos
AUDIO_BASE64=$(base64 -i reunion-larga.mp3)
curl -X POST 'https://os.utilia.ai/api/external/v1/tickets/ai-transcribe-async' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-transcribe-async-001' \
-d "{
\"audioBase64\": \"${AUDIO_BASE64}\",
\"audioFilename\": \"reunion-larga.mp3\"
}"Recibir el resultado por SSE
/api/external/v1/tickets/ai-transcribe/:jobId/streamConexión SSE que envía el progreso y el texto de una transcripción en segundo plano. Permiso: support:tickets:ai.
Parámetros de ruta
- jobIdstringobligatorio
- Identificador del trabajo que devolvió POST /ai-transcribe-async
Cabeceras
- X-Api-Keystring
- Clave de API externa (formato: utl_xxx). Obligatoria salvo que la envíes en el parámetro
?apiKey=, admitido solo en peticiones GET con Accept: text/event-stream como esta. Una clave en la dirección queda en registros y proxies: mejor la cabecera. - Acceptstring
- Recomendado: text/event-stream. Imprescindible si te autenticas con
?apiKey=en la dirección.
Formato de la respuesta
El servidor responde con Content-Type: text/event-stream y emite bloques separados por \n\n. Todos los bloques se emiten como un único tipo de mensaje SSE (sin línea event:). El tipo lógico viene dentro del JSON, en el campo event:
data: {"event":"progress","progress":30}
data: {"event":"completed","result":{"transcription":"Hola, quiero reportar un problema con la exportación de informes..."}}
Formas que no funcionan (porque enlazan por el tipo SSE, que aquí siempre es message):
es.addEventListener('completed', ...); // NUNCA se disparaif event.event == 'completed': # NUNCA se cumple con sseclient
...Lo correcto es usar el manejador genérico (onmessage o event.event == 'message') y leer el campo event del JSON.
Tipos de eventos (campo event dentro del JSON)
event | Descripción | Resto del JSON |
|---|---|---|
progress | Progreso actual | progress: number (0-100) |
completed | La transcripción ha terminado | result: { transcription: string } |
failed | La transcripción ha fallado | error: string |
heartbeat | Señal para mantener viva la conexión, cada 30 segundos | (sin más campos) |
Ejemplos
curl -N -X GET 'https://os.utilia.ai/api/external/v1/tickets/ai-transcribe/job_tr_abc123/stream' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'Accept: text/event-stream' \
-H 'x-request-id: req-transcribe-stream-001'
# Salida real (sin líneas `event:`, el tipo va dentro del JSON):
# data: {"event":"progress","progress":30}
#
# data: {"event":"progress","progress":80}
#
# data: {"event":"completed","result":{"transcription":"Hola, quiero reportar un problema..."}}Consultar el estado de la transcripción
/api/external/v1/tickets/ai-transcribe/:jobIdEstado de una transcripción en segundo plano, como alternativa al SSE. Permiso: support:tickets:ai.
Parámetros de ruta
- jobIdstringobligatorio
- Identificador del trabajo que devolvió POST /ai-transcribe-async
Respuesta correcta (200 OK)
- statusstringobligatorio
- Estado del trabajo: pending, processing, completed, failed o not_found
- progressnumber
- Porcentaje de progreso (0-100). Solo presente en estados pending y processing
- transcriptionstring
- Texto transcrito. Solo presente cuando status es completed
- errorstring
- Mensaje de error. Solo presente cuando status es failed
Ejemplos
curl -X GET 'https://os.utilia.ai/api/external/v1/tickets/ai-transcribe/job_tr_abc123' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-transcribe-poll-001'
# Respuesta cuando está en progreso:
# {
# "status": "processing",
# "progress": 45
# }
# Respuesta cuando ha completado:
# {
# "status": "completed",
# "transcription": "Hola, quiero reportar un problema con la exportación de informes..."
# }Ejemplo completo: transcripción en segundo plano con SSE
const API_BASE = 'https://os.utilia.ai/api/external/v1/tickets';
const headers = {
'Content-Type': 'application/json',
'X-Api-Key': 'utl_tu_clave_de_api',
};
// 1. Obtener el audio (ejemplo: desde un input file)
const fileInput = document.querySelector('input[type="file"]');
const audioFile = fileInput.files[0];
const arrayBuffer = await audioFile.arrayBuffer();
const base64 = btoa(
new Uint8Array(arrayBuffer).reduce(
(data, byte) => data + String.fromCharCode(byte),
''
)
);
// 2. Decidir síncrono o asíncrono según el tamaño
const MAX_SYNC_SIZE = 10 * 1024 * 1024; // 10 MB
if (audioFile.size <= MAX_SYNC_SIZE) {
// Transcripción síncrona
const response = await fetch(`${API_BASE}/ai-transcribe`, {
method: 'POST',
headers,
body: JSON.stringify({
audioBase64: base64,
audioFilename: audioFile.name,
}),
});
const { transcription } = await response.json();
console.log('Transcripción (síncrona):', transcription);
} else {
// Transcripción asíncrona con streaming (fetch + ReadableStream)
const enqueue = await fetch(`${API_BASE}/ai-transcribe-async`, {
method: 'POST',
headers,
body: JSON.stringify({
audioBase64: base64,
audioFilename: audioFile.name,
}),
});
const { jobId } = await enqueue.json();
// Importante: el SSE emite TODOS los eventos como `message`. Lee el
// campo `event` del JSON; no uses addEventListener('completed', ...).
const stream = await fetch(`${API_BASE}/ai-transcribe/${jobId}/stream`, {
method: 'GET',
headers: {
'X-Api-Key': 'utl_tu_clave_de_api',
Accept: 'text/event-stream',
},
});
const reader = stream.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
outer: while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let sep = buffer.indexOf('\n\n');
while (sep !== -1) {
const bloque = buffer.slice(0, sep);
buffer = buffer.slice(sep + 2);
sep = buffer.indexOf('\n\n');
const dataLines = bloque
.split('\n')
.filter((l) => l.startsWith('data:'))
.map((l) => l.slice(5).trimStart());
if (dataLines.length === 0) continue;
const data = JSON.parse(dataLines.join('\n'));
if (data.event === 'completed') {
console.log('Transcripción (asíncrona):', data.result.transcription);
reader.cancel();
break outer;
}
if (data.event === 'failed') {
console.error('Error:', data.error);
reader.cancel();
break outer;
}
}
}
}La extensión de audioFilename es la que indica el formato del audio, así que ponla siempre y que sea la correcta. Las grabaciones de navegador (WebM) y los formatos habituales como MP4 o MP3 se admiten.
Siguiente paso
Con el texto transcrito, pide las sugerencias con IA para rellenar el formulario o crea el ticket directamente con los endpoints de tickets.