Construir con una API de transcripción: primera integración
apidesarrolladorestutorial

Construir con una API de transcripción: primera integración

BMMamane B. MoussaMay 26, 2026Updated July 2, 202611 min read

Summarize this article with:

La integración en cinco pasos

Puedes añadir transcripción a cualquier backend en menos de una hora: consigue una clave de API, envía el audio por POST (mediante subida de archivo o URL), haz sondeo o recibe un webhook al completarse, obtén el resultado y expórtalo al formato que necesites. Los patrones que se muestran aquí usan la API de ConvertAudioToText, pero el esquema de enviar-y-luego-sondear es estándar en todos los proveedores de transcripción asíncrona. Si todavía estás eligiendo un proveedor, consulta primero la comparación de APIs de transcripción 2026.

Los cinco pasos son: autenticarte, enviar un trabajo, obtener el resultado, manejar los errores y exportar. Cada sección siguiente cubre un paso con código ejecutable.

Paso 1: Obtener una clave de API

El acceso a la API de ConvertAudioToText requiere el plan Business. Dentro del panel, navega a Settings, luego a API Keys y crea una nueva clave. Dale un nombre descriptivo vinculado a la integración ("production-backend", "staging-ingest"). La clave se muestra una sola vez, así que cópiala de inmediato.

Formato de la clave: ck_live_ seguido de 64 caracteres hexadecimales. Trátala como una contraseña. Guárdala en una variable de entorno, nunca en el control de versiones.

Cada solicitud usa Authorization: Bearer <key>:

curl https://api.convertaudiototext.com/api/v1/result/some-job-id \
  -H "Authorization: Bearer ck_live_..."

En Node.js:

const headers = { 'Authorization': `Bearer ${process.env.CATT_API_KEY}` };

En Python:

headers = {'Authorization': f'Bearer {os.environ["CATT_API_KEY"]}'}

Paso 2: Enviar un trabajo de transcripción

El endpoint es POST /api/v1/transcribe. Devuelve HTTP 201 de inmediato con un job_id; la transcripción real es asíncrona.

Existen dos vías de envío: subida de archivo y URL. Ambas devuelven la misma estructura de respuesta.

Subida de archivo

Úsala cuando el audio esté en tu equipo local o lo recibas como una subida multipart desde tus usuarios.

import FormData from 'form-data';
import { createReadStream } from 'fs';
import fetch from 'node-fetch';

async function submitFileJob(filePath, language = 'en') {
  const form = new FormData();
  form.append('source', 'upload');
  form.append('language', language);
  form.append('file', createReadStream(filePath));

  const res = await fetch('https://api.convertaudiototext.com/api/v1/transcribe', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${process.env.CATT_API_KEY}` },
    body: form
  });

  if (!res.ok) throw new Error(`Submit failed: ${res.status}`);
  const { job_id, status } = await res.json();
  return { job_id, status };
}

Envío por URL

Úsalo cuando el audio ya esté en almacenamiento en la nube (S3, R2, GCS o cualquier URL HTTPS pública). El servidor descarga a gran ancho de banda desde el origen, lo cual es más rápido que volver a subirlo desde tu máquina.

async function submitURLJob(audioURL, language = 'en') {
  const res = await fetch('https://api.convertaudiototext.com/api/v1/transcribe', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CATT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      source: 'url',
      input_url: audioURL,
      language: language
    })
  });

  if (!res.ok) throw new Error(`Submit failed: ${res.status}`);
  const { job_id, status } = await res.json();
  return { job_id, status };
}

Ambos devuelven { job_id: "uuid-string", status: "queued" }.

Herramienta de subida de audio de ConvertAudioToText
Herramienta de subida de audio de ConvertAudioToText

La herramienta de subida de audio que impulsa la API. El mismo pipeline de trabajos funciona tanto si usas la interfaz web como la API.

Paso 3: Obtener el resultado

Un trabajo completado está disponible en GET /api/v1/result/{job_id}. Hay dos patrones para esperar: sondeo (polling) y webhooks.

Sondeo (polling)

Haz sondeo cada 5 segundos hasta que status sea "completed" o "failed". El endpoint responde al instante; la espera se debe por completo al lado de la transcripción.

async function pollForResult(jobId, maxWaitMs = 600000) {
  const start = Date.now();

  while (Date.now() - start < maxWaitMs) {
    const res = await fetch(
      `https://api.convertaudiototext.com/api/v1/result/${jobId}`,
      { headers: { 'Authorization': `Bearer ${process.env.CATT_API_KEY}` } }
    );
    const data = await res.json();

    if (data.status === 'completed') return data;
    if (data.status === 'failed') throw new Error(data.error || 'Job failed');

    await new Promise(r => setTimeout(r, 5000));
  }

  throw new Error('Timeout waiting for transcription result');
}

No hagas sondeo más rápido de una vez por segundo por trabajo. El límite global es de 100 solicitudes por minuto por IP, así que un bucle muy ajustado sobre muchos trabajos lo activará.

Webhooks

Para un mejor uso de recursos a escala, los webhooks sustituyen por completo al sondeo. Registra una URL de webhook una sola vez:

curl -X POST https://api.convertaudiototext.com/api/v1/dashboard/webhooks \
  -H "Authorization: Bearer $CATT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/transcription",
    "events": ["job.completed", "job.failed"]
  }'

La respuesta incluye un campo secret. Guárdalo. El servidor hará POST a tu URL en cada evento coincidente. El payload tiene este aspecto:

{
  "event": "job.completed",
  "timestamp": "2026-07-01T14:22:05Z",
  "data": {
    "job_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "duration": 1842,
    "result_url": "https://api.convertaudiototext.com/api/v1/result/550e8400-...",
    "completed_at": "2026-07-01T14:22:04Z"
  }
}

Verifica cada entrega con HMAC-SHA256. El servidor envía la firma en X-Webhook-Signature como sha256=<hex>:

import crypto from 'crypto';

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// En tu handler de Express/Fastify:
app.post('/webhooks/transcription', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-webhook-signature'];
  if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = JSON.parse(req.body);
  if (event === 'job.completed') {
    // obtén la transcripción completa desde data.result_url
  }
  res.status(200).end();
});

Los webhooks requieren el plan Business y una URL HTTPS accesible públicamente. Para una comparación más profunda de ambos enfoques, consulta la guía de webhook frente a sondeo para transcripciones.

Paso 4: Interpretar el resultado

La respuesta de un trabajo completado anida la transcripción bajo una clave transcript. La estructura exacta la controla el formateador del lado del servidor, pero los campos que siempre encontrarás son:

{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "language": "en",
  "duration": 1842,
  "filename": "interview.mp3",
  "transcript": {
    "text": "Hello, welcome to the show...",
    "summary": "An interview about...",
    "confidence": 0.97,
    "word_count": 4210,
    "timeline": [
      {
        "speaker": "Speaker 0",
        "text": "Hello, welcome to the show.",
        "start": 0.12,
        "end": 1.87
      }
    ],
    "topics": [...],
    "sentiments": [...]
  }
}

El campo text es la transcripción completa concatenada. El array timeline te da las intervenciones atribuidas a cada hablante con marcas de tiempo. Los campos summary, topics y sentiments se rellenan para audio en inglés en los planes que incluyen análisis con IA; se eliminan o no aparecen en el plan Starter.

Paso 5: Formatos de exportación

Descarga archivos formateados mediante:

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/srt" \
  -H "Authorization: Bearer $CATT_API_KEY" > captions.srt  # SRT para subtítulos de vídeo

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/vtt" \
  -H "Authorization: Bearer $CATT_API_KEY" > captions.vtt  # WebVTT

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/txt" \
  -H "Authorization: Bearer $CATT_API_KEY" > transcript.txt  # Texto plano con etiquetas de hablante

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/json" \
  -H "Authorization: Bearer $CATT_API_KEY" > transcript.json  # JSON estructurado

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/docx" \
  -H "Authorization: Bearer $CATT_API_KEY" > transcript.docx  # DOCX

Todos los endpoints de exportación requieren un plan de pago. Las solicitudes del nivel gratuito devuelven HTTP 402 con un campo upgrade_url. Para crear flujos de trabajo de subtítulos a escala, los endpoints SRT y VTT funcionan bien en pipelines por lotes.

Manejo de errores

Tres categorías de errores que debes manejar, cada una con una estrategia de recuperación distinta.

Los errores 4xx indican un fallo en tu código. Autenticación incorrecta (401), campos obligatorios ausentes (400), cuota agotada (402) o límite de tasa alcanzado (429). No reintentes sin corregir la solicitud. Ante un 429, lee el campo retry_after del cuerpo de la respuesta.

Los errores 5xx son transitorios. Reintenta con retroceso exponencial. La mayoría desaparece en menos de 30 segundos.

Fallos a nivel de trabajo. Un trabajo puede enviarse correctamente (HTTP 201) pero fallar después durante el procesamiento. status pasa a ser "failed" y error contiene el motivo. Causas habituales: audio corrupto, grabaciones completamente silenciosas, códecs no compatibles. Reintenta el trabajo solo si la fuente de audio es válida.

async function transcribeWithRetry(audioPath, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      const { job_id } = await submitFileJob(audioPath);
      return await pollForResult(job_id);
    } catch (err) {
      // 4xx: corrige la solicitud, no reintentes
      if (err.status >= 400 && err.status < 500) throw err;
      // Último intento: rendirse
      if (attempt === maxAttempts - 1) throw err;
      // 5xx: espera e inténtalo de nuevo
      const delayMs = 1000 * Math.pow(2, attempt);
      await new Promise(r => setTimeout(r, delayMs));
    }
  }
}

Límites de tasa

Los límites que se aplican a las solicitudes de API autenticadas:

CapaLímiteÁmbito
Global100 solicitudes/minPor dirección IP
Trabajos de transcripción10 envíos/minPor usuario autenticado
Subida multipart (llamadas sign)1000/minPor IP, techo independiente

El límite de trabajos de transcripción significa que puedes enviar hasta 10 trabajos por minuto, no 10 concurrentes; los trabajos que superen ese umbral se ponen en cola en el servidor.

Alcanzar cualquiera de los dos límites devuelve HTTP 429 con retry_after en segundos en el cuerpo de la respuesta.

Un ejemplo completo y funcional

import { createReadStream } from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';

const API_KEY = process.env.CATT_API_KEY;
const BASE    = 'https://api.convertaudiototext.com/api/v1';

async function submit(filePath, language = 'en') {
  const form = new FormData();
  form.append('source', 'upload');
  form.append('language', language);
  form.append('file', createReadStream(filePath));

  const res = await fetch(`${BASE}/transcribe`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${API_KEY}` },
    body: form
  });

  if (!res.ok) throw Object.assign(new Error('Submit failed'), { status: res.status });
  return res.json();
}

async function poll(jobId, maxWaitMs = 600000) {
  const deadline = Date.now() + maxWaitMs;

  while (Date.now() < deadline) {
    const res = await fetch(`${BASE}/result/${jobId}`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` }
    });
    const data = await res.json();

    if (data.status === 'completed') return data;
    if (data.status === 'failed') throw new Error(data.error || 'Transcription failed');

    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error('Timeout');
}

export async function transcribeFile(filePath, language = 'en') {
  const { job_id } = await submit(filePath, language);
  const result     = await poll(job_id);
  return result.transcript?.text ?? '';
}

A partir de aquí, añade caché para los resultados de transcripción y así evitarás reprocesar archivos idénticos, y sustituye el sondeo por webhooks cuando tengas más de unos pocos trabajos concurrentes.

Mi opinión: el esquema de enviar-y-sondear es el mismo en todos los grandes proveedores de transcripción asíncrona, así que el código cliente se traslada casi textualmente. Las partes que difieren son el formato de la cabecera de autenticación, los nombres de los campos en la respuesta y si las funciones avanzadas como las etiquetas de hablante requieren un plan superior. Verifica cada punto contra la documentación actual del proveedor antes de lanzar a producción.

Si solo necesitas una transcripción limpia de forma ocasional sin gestionar claves de API ni niveles de planes, ConvertAudioToText te permite subir, transcribir y copiar tu transcripción gratis y sin configuración.

Preguntas frecuentes

¿Qué plan necesito para usar la API?

La creación de claves de API en ConvertAudioToText requiere el plan Developer o Business. Developer incluye 600 minutos al mes; Business es ilimitado. Consulta /pricing para ver los niveles más recientes antes de comprar.

¿Cuál es el límite de tasa para los trabajos de transcripción?

El límite global es de 100 solicitudes por minuto por dirección IP. El envío de trabajos de transcripción está además limitado a 10 trabajos por minuto por usuario. Al alcanzar cualquiera de los dos límites se devuelve HTTP 429 con una cabecera Retry-After.

¿Sondeo (polling) o webhooks: cuál debería usar?

El sondeo funciona para integraciones de bajo volumen u ocasionales y no requiere un endpoint público. Los webhooks son mejores cuando tienes varios trabajos concurrentes: en lugar de sondear N trabajos cada pocos segundos, tu servidor recibe un POST por cada finalización. Los webhooks requieren el plan Business y una URL HTTPS accesible públicamente.

¿Cómo verifico que el payload de un webhook es auténtico?

Cada registro de webhook devuelve una cadena secreta. Cuando el servidor entrega un payload, calcula el HMAC-SHA256 del cuerpo JSON sin procesar usando ese secreto y envía el resultado en la cabecera X-Webhook-Signature como sha256=hex. Recalcula el mismo HMAC en tu lado y compáralos. Rechaza cualquier solicitud donde no coincidan.

¿Qué formatos de exportación admite la API?

Los trabajos completados pueden exportarse como SRT, VTT, TXT, JSON, DOCX y PDF mediante GET /api/v1/result//. Todos los endpoints de exportación requieren un plan de pago; las solicitudes del nivel gratuito devuelven HTTP 402.

Fuentes

Try transcription free

Convert any audio or video to clean, unwatermarked text — speaker labels, timestamps, and AI summaries included. First 10 minutes free, no account.

Related Articles