Crea un pipeline de transcripción totalmente automatizado con n8n
La mayoría de los equipos todavía transcribe a mano: alguien descarga la grabación, la sube a algún sitio, espera, descarga un SRT, lo renombra y lo deja en la carpeta correcta. Funciona hasta que ocurre cuarenta veces por semana.
Este tutorial sustituye a esa persona por un flujo de trabajo. Cuando llega una grabación nueva, se transcribe, se subtitula y se entrega — sin que nadie esté pendiente. Usaremos n8n porque funciona en cualquier parte, puede autoalojarse dentro de tu propia infraestructura y se comunica con cualquier API HTTP — incluida la nuestra.
Al terminar tendrás un flujo de trabajo que:
- se activa cuando aparece un nuevo archivo de video o audio
- lo envía a la API de Inwista para transcribirlo
- espera a que termine el trabajo, sin apostar por cuánto tardará
- descarga el SRT terminado
- lo entrega donde tu equipo lo necesite
Todo lo que sigue funciona sobre la API pública Inwista API v1. Sin plugins ni nodos de código personalizado.
Antes de empezar
Necesitas tres cosas:
- Una instancia de n8n — en la nube o autoalojada, versión 1.x
- Una clave de API de Inwista — crea una en Mi espacio de trabajo → Claves de API. Empieza por
inw_live_ - Medios accesibles para la API — la API espera una URL
httpspública, así que los archivos en Dropbox, Google Drive, S3 o tu CMS necesitan un enlace compartible o firmado
Una nota sobre el coste antes de construir algo que funcione sin supervisión: la transcripción se factura a 4 créditos por minuto iniciado de material, y se cobra al aceptar el trabajo. Los trabajos fallidos se reembolsan automáticamente. Prueba el flujo con un par de archivos cortos antes de apuntarlo a todo tu archivo.
Paso 1: elige tu disparador
El pipeline arranca con el evento que significa «hay algo nuevo que transcribir». Las opciones más habituales:
- Google Drive Trigger / Dropbox Trigger — una carpeta vigilada donde tu equipo deja las grabaciones
- Webhook — tu propio CMS o LMS llama a n8n cuando termina una subida
- Schedule Trigger — consulta un feed RSS, un alojamiento de podcasts o una base de datos con filas sin procesar
- Manual Trigger — para construir y probar, que es por donde vamos a empezar
Añade un Manual Trigger por ahora. Cámbialo por el definitivo cuando el resto funcione.
Sea cual sea, el trabajo del disparador es producir una sola cosa: una URL públicamente accesible al archivo de medios. Guárdala en un campo llamado mediaUrl para que el resto del tutorial encaje con tu flujo.
Paso 2: envía la transcripción
Añade un nodo HTTP Request llamado Submit transcription.
- Método —
POST - URL —
https://api.inwista.ai/v1/transcriptions - Autenticación — Generic Credential Type → Header Auth
- Nombre de la cabecera —
Authorization - Valor de la cabecera —
Bearer inw_live_tu_clave_aqui - Send body — activado, JSON
Cuerpo:
{
"source_url": "{{ $json.mediaUrl }}",
"language": "es",
"diarization": true,
"metadata": { "source": "n8n", "folder": "{{ $json.folderName }}" }
}
Tres campos que conviene entender:
languagees el idioma hablado en el archivo, no el idioma que quieres obtener. Transcribir en el idioma de origen es lo que produce marcas de tiempo precisas y texto limpio; la traducción llega después, como un paso aparte, sobre la transcripción terminada. Si tu pipeline maneja varios idiomas, asigna a este campo la carpeta o los metadatos del disparador.
diarizationactiva las etiquetas de hablante. Déjalo desactivado en contenido de una sola voz — añade un tiempo de proceso que no necesitas.
metadataes tuyo. Hasta 1 KB de lo que quieras, devuelto tal cual en cada lectura. Úsalo para llevar los identificadores que importan en tus propios sistemas — el ID de un curso, un número de expediente, el slug de un episodio — para que los pasos posteriores no tengan que reconstruir el contexto.
Añade una cabecera más ya que estás:
Idempotency-Key—{{ $execution.id }}
Si n8n reintenta el nodo tras un fallo de red, la API reconoce la clave y devuelve el trabajo original en lugar de arrancar — y cobrar — un segundo.
La respuesta llega de inmediato con status: "processing" y un id. La transcripción no ha terminado: se ha aceptado.
Paso 3: espera a que termine, bien
Aquí es donde fallan la mayoría de los pipelines. Un «espera cinco minutos» fijo se queda corto para una clase magistral y sobra para una nota de voz. Consulta el estado en su lugar.
Añade un nodo Wait (Wait 15s) configurado a 15 segundos.
Añade un nodo HTTP Request (Check status):
- Método —
GET - URL —
https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }} - Autenticación — la misma cabecera que antes
Añade un nodo IF (Is it done?) con la condición:
{{ $json.status }} equals completed
Conecta la rama false de vuelta a Wait 15s. Ese es tu bucle: comprobar, esperar, volver a comprobar, hasta que el trabajo indique completed. Conecta la rama true al siguiente paso.
La respuesta también trae un número progress que refleja la posición real del pipeline, así que si quieres un indicador de avance en Slack o en tu propio panel, ya lo tienes.
Dos cosas que añadir antes de acercarte a producción:
- Gestiona los fallos. Añade un segundo IF que compruebe
{{ $json.status }} equals failedy lo dirija a tu sistema de alertas. Los trabajos fallidos se reembolsan automáticamente, pero aun así quieres enterarte. - Limita el bucle. La protección de bucles de n8n ayuda, pero un techo explícito — un contador que se rinde tras, digamos, 80 iteraciones — convierte un trabajo atascado en una alerta en lugar de una ejecución que corre toda la noche.
Paso 4: recoge los subtítulos
Añade un nodo HTTP Request llamado Get SRT:
- Método —
GET - URL —
https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt - Response format — File (o Text, si quieres el contenido dentro del flujo)
El endpoint devuelve el propio archivo de subtítulos, no un envoltorio JSON — así que la salida del nodo está lista para escribirse en disco, adjuntarse a un correo o subirse.
Cambia format según lo que necesite el destino:
Formato — para qué sirve
srt— reproductores de video, editores de montaje, YouTube, la mayoría de CMSvtt— etiqueta<track>de HTML5, reproductores webtxt— índices de búsqueda, pipelines de LLM, documentaciónjson— marcas de tiempo por palabra, etiquetas de hablante, renderizado propio
Cada formato es una vista sobre el mismo trabajo terminado. Descargar cuatro no cuesta nada extra.
Paso 5: entrégalo
El último nodo es lo que «terminado» signifique para tu equipo:
- Google Drive / Dropbox / S3 — escribe el archivo junto al video original
- Slack — publica la transcripción en el canal que la pidió
- HTTP Request — mándala a tu CMS, tu LMS o tu campo de subtítulos
- Postgres / Airtable / Notion — guarda la versión
txtcomo texto consultable
Si tu destino es uno de los que Inwista ya integra de forma nativa — Google Drive, OneDrive, SharePoint, Dropbox, Box, YouTube, Vimeo, Wistia y otros — plantéate saltarte este nodo por completo y configurar la entrega en la nube en Mi espacio de trabajo → Integraciones. Los archivos llegan entonces automáticamente en cada trabajo terminado, con flujo o sin él.
Sube de nivel: traduce antes de entregar
Inserta dos nodos antes de la entrega y el mismo pipeline produce subtítulos en tantos idiomas como necesites.
POST /v1/transcriptions/{id}/translate con:
{ "target_language": "no" }
Devuelve 202 Accepted — la traducción se aplica sobre la transcripción terminada, así que los tiempos ya son correctos y solo cambia el texto. Consulta GET /v1/transcriptions/{id}/translations/no igual que consultabas el trabajo y luego recoge:
GET /v1/transcriptions/{id}/captions?format=srt&language=no
Repite eso sobre una lista de idiomas de destino y una sola grabación se convierte en un juego completo de subtítulos multilingües en una única ejecución. Ten en cuenta que la petición de idioma es estricta: pedir un idioma sin traducción terminada devuelve un error explícito en lugar de entregarte en silencio el idioma de origen, que es justo lo que quieres en un pipeline sin supervisión.
Sube de nivel: subtítulos con calidad de emisión
Una transcripción en bruto es literal. Los subtítulos son un oficio: longitud de línea, velocidad de lectura, dónde se parte una frase entre dos rótulos.
POST /v1/transcriptions/{id}/enhance pasa la transcripción terminada por ese tratamiento — condensa el texto, reequilibra los saltos de línea, da formato a los diálogos y aplica duraciones mínima y máxima de bloque:
{
"settings": {
"maxLinesPerBlock": "2",
"maxCharactersPerLine": 42,
"textCondensation": "smart",
"speakerDialogueFormat": "hyphens",
"gapBetweenBlocks": "broadcasting"
}
}
Devuelve 202; consulta GET /v1/transcriptions/{id}/enhancements/{enhancementId} hasta que termine. Después, los endpoints de subtítulos sirven automáticamente la versión mejorada — sin cambiar nada en tu nodo de entrega.
Grabaciones sensibles
Si tu pipeline procesa material que preferirías que no guardáramos — entrevistas con pacientes, grabaciones jurídicas, reuniones internas — añade un campo al envío del paso 2:
{
"source_url": "{{ $json.mediaUrl }}",
"language": "es",
"retention": "none"
}
Con retention: "none", el medio original se elimina en cuanto termina la transcripción. La transcripción, los subtítulos, las traducciones y cualquier mejora posterior siguen funcionando con normalidad — solo desaparecen el audio y el video. También existe store_media: false, que conserva el archivo para procesarlo pero no genera copias de reproducción.
Y cuando el periodo de conservación de un trabajo termina por tu parte, DELETE /v1/transcriptions/{id} borra todo lo relacionado — medios, transcripción, revisiones, traducciones — en una sola llamada. Montar un flujo de n8n programado que elimine los trabajos más antiguos que tu política son unos cuatro nodos, y convierte tu política de retención en algo que puedes demostrar en vez de describir.
Webhooks: el patrón complementario
Consultar el estado es el control de flujo correcto dentro de una sola ejecución de n8n: es autónomo, no necesita una URL pública y mantiene todo el pipeline en un único sitio que puedes depurar.
Los webhooks resuelven otro problema: llevar el trabajo terminado a un receptor central, venga de donde venga. Las grabaciones que tus compañeros suben desde el panel, los trabajos enviados por otro sistema, las exportaciones que terminan horas después — todo puede aterrizar en un único endpoint en lugar de que cada flujo se ocupe del suyo. Configúralo en Mi espacio de trabajo → Integraciones.
Apúntalo a un nodo Webhook de n8n y recibirás un evento transcript.completed firmado:
{
"event": "transcript.completed",
"id": "evt_...",
"timestamp": 1786902819,
"workspaceId": "...",
"project": {
"id": "...",
"name": "reunion-consejo-agosto.mp4",
"language": "es",
"durationSeconds": 3184
},
"files": [
{ "format": "srt", "name": "reunion-consejo-agosto.srt", "url": "https://...", "expiresAt": 1786989219 }
]
}
Cada petición va firmada con HMAC-SHA256 sobre el cuerpo en bruto, en la cabecera X-Inwista-Signature como sha256=<hex>. Verifícala antes de fiarte del payload — en n8n basta con un nodo Crypto y una comparación IF. Las URL de los archivos van firmadas y valen 24 horas, así que descarga lo que necesites en vez de guardar los enlaces.
La entrega se reintenta tres veces, y un endpoint que falla de forma persistente se desactiva automáticamente con el motivo visible en tus ajustes de integración — así, un receptor roto aparece como un estado que puedes ver, y no como eventos que desaparecen en silencio.
Cosas que te morderán tarde o temprano
Límites de peticiones. 300 lecturas y 60 escrituras por minuto y por clave. Generoso para un uso normal, fácil de alcanzar si lanzas un archivo entero en paralelo sin una cola. Si estás recuperando cientos de archivos, procésalos por lotes.
Las URL de origen tienen que ser accesibles. La API sondea el medio antes de aceptar el trabajo — así se conocen de antemano la duración y el coste. Un enlace de Drive que exige iniciar sesión devuelve unreadable_source, y no se cobra nada. Usa URL directas o firmadas.
Los errores están estructurados. Cada fallo devuelve el mismo envoltorio:
{ "error": { "code": "insufficient_credits", "message": "..." } }
Ramifica según code, nunca según el texto del mensaje. Los códigos se añaden dentro de v1 y nunca se renombran; los mensajes sí pueden reformularse.
Las claves de idempotencia son por petición, no por archivo. Te protegen de un cobro doble al reintentar la misma ejecución de un nodo. Deduplicar la misma grabación enviada dos veces por dos ejecuciones distintas es tarea de tu flujo — una tabla de referencia indexada por el ID del archivo es la respuesta habitual.
El flujo terminado
Este es el esqueleto para importar y adaptar. Sustituye la referencia de credenciales por tu propia credencial de Header Auth en lugar de pegar una clave en el nodo.
{
"name": "Inwista — automated transcription",
"nodes": [
{
"parameters": {},
"id": "trigger",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"method": "POST",
"url": "https://api.inwista.ai/v1/transcriptions",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\"source_url\": \"{{ $json.mediaUrl }}\", \"language\": \"es\", \"diarization\": true}",
"options": {}
},
"id": "submit",
"name": "Submit transcription",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [220, 0]
},
{
"parameters": { "amount": 15 },
"id": "wait",
"name": "Wait 15s",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [440, 0]
},
{
"parameters": {
"url": "=https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}",
"options": {}
},
"id": "status",
"name": "Check status",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [660, 0]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "version": 2 },
"conditions": [
{
"leftValue": "={{ $json.status }}",
"rightValue": "completed",
"operator": { "type": "string", "operation": "equals" }
}
],
"combinator": "and"
},
"options": {}
},
"id": "isdone",
"name": "Is it done?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [880, 0]
},
{
"parameters": {
"url": "=https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt",
"options": { "response": { "response": { "responseFormat": "text" } } }
},
"id": "srt",
"name": "Get SRT",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1100, -100]
}
],
"connections": {
"When clicking Test workflow": { "main": [[{ "node": "Submit transcription", "type": "main", "index": 0 }]] },
"Submit transcription": { "main": [[{ "node": "Wait 15s", "type": "main", "index": 0 }]] },
"Wait 15s": { "main": [[{ "node": "Check status", "type": "main", "index": 0 }]] },
"Check status": { "main": [[{ "node": "Is it done?", "type": "main", "index": 0 }]] },
"Is it done?": {
"main": [
[{ "node": "Get SRT", "type": "main", "index": 0 }],
[{ "node": "Wait 15s", "type": "main", "index": 0 }]
]
}
}
}
Seis nodos. Todo lo demás — traducción, mejora, entrega, borrado — cuelga de este mismo esqueleto.
Lo que esto cambia de verdad
Lo interesante no es que la transcripción se automatice. Es lo que deja de ser una decisión.
Cuando unos subtítulos cuestan la tarde de una persona, se racionan: los videos importantes los llevan, el resto no. Cuando no cuestan nada por archivo y llegan antes de que a nadie se le ocurra pedirlos, dejan de ser un proyecto y pasan a ser una propiedad de tus contenidos — cada grabación consultable, cada video accesible, cada curso conforme, sin necesidad de una reunión.
Eso vale más que las horas que ahorra.
¿Listo para construirlo? Crea una clave de API — el plan gratuito basta para recorrer esto de principio a fin. La referencia completa de endpoints está en la documentación de la API.