API de Transcodificación: Convierte vídeo a escala en tu propio bucket
La API de Transcodificación convierte un vídeo de origen en las versiones que realmente publicas (archivos MP4 en varias resoluciones, una escalera adaptativa HLS, sprites de miniaturas, GIFs de previsualización) y los escribe directamente en tu propio bucket compatible con S3. Tú nos das una entrada y un destino; nosotros leemos, codificamos y entregamos. Nunca guardamos tu contenido.
Es el patrón habitual para plataformas VOD, pipelines de contenido generado por usuarios, procesado de cursos/webinars, previsualizaciones para redes sociales, y cualquier caso en el que tengas una pila de archivos de origen y necesites una salida multiformato y consistente sin ejecutar ffmpeg tú mismo.
Cómo funciona (en un párrafo)
Creas un trabajo (job): una entrada (una URL pública o un objeto de tu bucket S3), un destino de salida (tu bucket S3 + un prefijo de ruta) y una lista de outputs, los formatos que quieres. Obtenemos el origen, codificamos cada versión en paralelo, empaquetamos los resultados y los subimos bajo tu prefijo de destino. El trabajo pasa por queued → analyzing → encoding → finalizing → completed, exponiendo un progreso de 0–100 durante todo el proceso. Al terminar podemos avisar a un webhook que indiques, y los archivos generados quedan en tu bucket. Tus credenciales de almacenamiento se usan solo para ese trabajo y se guardan cifradas; nunca las devolvemos ni conservamos la salida.
Antes de empezar
- Consigue un token de API en el panel: my.cubepath.com → Cuenta → Tokens de API. Envíalo en cada petición como
Authorization: Bearer <token>(oX-API-Key: <token>). - Scopes: consultar el estado necesita
transcoder:read; crear o cancelar trabajos necesitatranscoder:write. - Las peticiones de escritura (
POST,DELETE) deben llevar también la cabeceraX-Requested-With: XMLHttpRequest. - Estado de la cuenta: tu organización debe estar verificada, sin suspensión y con saldo positivo para crear trabajos.
- URL base:
https://api.cubepath.com.
El almacenamiento lo pones tú. Tanto la entrada (cuando es S3) como la salida apuntan a cualquier endpoint compatible con S3: CubePath Object Storage, AWS S3, Cloudflare R2, Backblaze B2, Wasabi, MinIO. El
secret_keyque envías se cifra en reposo y nunca se devuelve en ninguna respuesta de la API.
Crea tu primer trabajo
Un trabajo mínimo: coger un MP4 y producir un MP4 a 1080p y otro a 720p en tu bucket.
cURL
curl -X POST https://api.cubepath.com/transcoder/jobs \
-H "Authorization: Bearer $CUBEPATH_TOKEN" \
-H "X-Requested-With: XMLHttpRequest" \
-H "Content-Type: application/json" \
-d '{
"input": {
"source": "url",
"url": "https://example.com/source/charla.mp4"
},
"output": {
"s3": {
"endpoint": "https://s3.eu-central-1.amazonaws.com",
"region": "eu-central-1",
"bucket": "mi-media",
"path": "charlas/charla-42/",
"access_key": "AKIA...",
"secret_key": "..."
}
},
"outputs": [
{ "type": "file", "container": "mp4", "codec": "h264", "height": 1080 },
{ "type": "file", "container": "mp4", "codec": "h264", "height": 720 }
]
}'
La respuesta es el trabajo creado, con su uuid y status: "queued". Guarda el uuid; es como consultas el progreso y obtienes las salidas.
Python
import os, requests
API = "https://api.cubepath.com"
HEADERS = {
"Authorization": f"Bearer {os.environ['CUBEPATH_TOKEN']}",
"X-Requested-With": "XMLHttpRequest",
}
job = requests.post(f"{API}/transcoder/jobs", headers=HEADERS, json={
"input": {"source": "url", "url": "https://example.com/source/charla.mp4"},
"output": {"s3": {
"endpoint": "https://s3.eu-central-1.amazonaws.com",
"region": "eu-central-1", "bucket": "mi-media", "path": "charlas/charla-42/",
"access_key": os.environ["S3_KEY"], "secret_key": os.environ["S3_SECRET"],
}},
"outputs": [
{"type": "file", "container": "mp4", "codec": "h264", "height": 1080},
{"type": "file", "container": "mp4", "codec": "h264", "height": 720},
],
}).json()
print(job["uuid"], job["status"])
Leer la entrada desde tu bucket en lugar de una URL
Pon input.source en "s3" e indica la ubicación del objeto:
"input": {
"source": "s3",
"s3": {
"endpoint": "https://s3.eu-central-1.amazonaws.com",
"region": "eu-central-1",
"bucket": "mis-subidas",
"path": "entrantes/charla.mp4", // la clave del objeto
"access_key": "AKIA...",
"secret_key": "..."
}
}
Tipos de salida
Cada entrada de outputs (de 1 a 20 por trabajo) es un formato. Combínalos libremente en un mismo trabajo; todos leen el origen una sola vez.
type | Parámetros clave | Produce |
|---|---|---|
file | codec (h264/h265/vp9/av1), container (mp4/webm/…, por defecto mp4), height (0 = mantener origen), opcional crf, preset, bitrate | output_<i>.<container> (p. ej. output_0.mp4) |
hls | ladder: un array de niveles [{ "height": 1080 }, { "height": 720 }, …] (obligatorio; sin ladder no se produce nada) | hls<i>/master.m3u8 + listas de reproducción y segmentos por nivel |
thumbnails | interval (segundos entre fotogramas) | thumbs<i>/thumb_0001.jpg, thumb_0002.jpg, … |
gif | duration (segundos), opcional start ("00:00:05") | output_<i>.gif |
Un trabajo que produce una escalera adaptativa más miniaturas y un GIF de previsualización:
"outputs": [
{ "type": "hls", "ladder": [ { "height": 1080 }, { "height": 720 }, { "height": 480 } ] },
{ "type": "thumbnails", "interval": 10 },
{ "type": "gif", "start": "00:00:05", "duration": 3 }
]
Los nombres de campo importan.
fileleecodec(novideo_codec).hlsnecesita unladder.codecaceptah264,h265,vp9,av1. Cualquier altura funciona, incluidas2160(4K) y4320(8K). Recuerda que 4K/8K y los códecs más lentos (h265,av1) consumen mucho más tiempo de procesamiento.
Consultar estado y progreso
curl https://api.cubepath.com/transcoder/jobs/<uuid> \
-H "Authorization: Bearer $CUBEPATH_TOKEN"
Campos clave de la respuesta:
| Campo | Significado |
|---|---|
status | queued → analyzing → encoding → finalizing → completed (o failed / canceled) |
progress | 0–100 |
total_segments / completed_segments | Cuánto de la codificación va hecho (los archivos grandes se dividen y codifican en paralelo) |
outputs | Se rellena al completarse el trabajo, con los archivos generados |
error | El motivo, si status es failed |
Consulta este endpoint por sondeo, o define un webhook_url (más abajo) para no tener que hacerlo.
Obtener las salidas
Cuando el trabajo está completed, obtén las ubicaciones de los artefactos generados:
curl https://api.cubepath.com/transcoder/jobs/<uuid>/outputs \
-H "Authorization: Bearer $CUBEPATH_TOKEN"
Todo se escribe bajo el prefijo path que fijaste en output.s3. Para un prefijo de salida charlas/charla-42/:
charlas/charla-42/output_0.mp4
charlas/charla-42/output_1.mp4
charlas/charla-42/hls0/master.m3u8
charlas/charla-42/thumbs1/thumb_0001.jpg
charlas/charla-42/output_2.gif
Los archivos viven en tu bucket; sírvelos directamente, o pon una zona CDN delante. Cualquier archivo temporal que usemos durante el proceso se limpia automáticamente tras un final correcto.
Webhooks
Añade "webhook_url": "https://example.com/hooks/transcode" a un trabajo y haremos un POST con JSON al terminar:
// éxito
{ "job_id": "…", "status": "completed" }
// fallo
{ "job_id": "…", "status": "failed", "error": "<motivo>" }
La entrega es de mejor esfuerzo y no se reintenta, así que trata el webhook como un aviso y mantén GET /transcoder/jobs/{uuid} como la fuente de verdad.
Procesar muchos archivos a la vez
POST /transcoder/jobs/batch crea hasta 1000 trabajos en una sola llamada. Comparten un mismo destino output y una misma especificación outputs; cada entrada añade un out_subpath opcional que se concatena al prefijo de salida para que los trabajos no se sobrescriban entre sí.
{
"output": { "s3": { /* …bucket de destino… */ "path": "biblioteca/" } },
"outputs": [ { "type": "file", "container": "mp4", "codec": "h264", "height": 720 } ],
"input_defaults": { "source": "s3", "s3": { /* …bucket/credenciales compartidas… */ "bucket": "mis-subidas" } },
"inputs": [
{ "path": "raw/a.mov", "out_subpath": "a/" },
{ "path": "raw/b.mov", "out_subpath": "b/" },
{ "url": "https://example.com/c.mp4", "out_subpath": "c/" }
]
}
Cada elemento de inputs[] es un bloque s3 completo, una url, o solo un path que hereda input_defaults.s3. La respuesta devuelve un batch_id y la lista de job_ids; lista un lote más tarde con GET /transcoder/jobs?batch_id=<id>.
Evitar trabajos duplicados (idempotencia)
Pasa una "idempotency_key": "tu-clave-unica" en POST /transcoder/jobs. Si ya existe un trabajo con esa clave en tu organización, devolvemos el trabajo existente en lugar de crear uno segundo, así es seguro reintentar una petición que quizá ya pasó.
Cancelar un trabajo
curl -X DELETE https://api.cubepath.com/transcoder/jobs/<uuid> \
-H "Authorization: Bearer $CUBEPATH_TOKEN" \
-H "X-Requested-With: XMLHttpRequest"
Esto detiene el trabajo aún no iniciado y fija status: "canceled". Un trabajo que ya esté completed o failed devuelve 409 Conflict.
Límites
| Límite | Valor |
|---|---|
| Salidas por trabajo | 20 |
| Entradas por petición de lote | 1000 |
| Trabajos activos (sin terminar) por organización | 20.000 |
| Ritmo de creación de trabajos | 60 peticiones / minuto |
| Ritmo de lotes | 10 peticiones / minuto |
Lo que la API de Transcodificación no hace
- No almacena tu contenido. La entrada y la salida viven en tus buckets. No hay un paso de "descargar desde CubePath"; los archivos terminados ya están en tu destino.
- No es un servidor de streaming. Produce HLS que puedes emitir; servirlo (y el control de acceso, URLs firmadas, etc.) es tarea de tu CDN/origen.
- No es un editor. Transcodifica, empaqueta, genera miniaturas y recorta a GIF; no corta, une, superpone ni corrige color.
Solución de problemas
| Síntoma | Causa probable |
|---|---|
400 al crear | Falta un campo según el tipo de origen: input.url es obligatorio cuando source es url, input.s3 cuando source es s3 |
400 "cannot target a private/internal address" | Una URL o endpoint S3 apunta a una IP privada/interna; usa un nombre de host público |
401 / 403 | Token ausente/caducado, o el token no tiene transcoder:read / transcoder:write |
403 solo en escritura | Falta la cabecera X-Requested-With: XMLHttpRequest, o la organización está sin verificar/suspendida/sin saldo |
429 | Has alcanzado el límite de ritmo de creación/lote o el tope de trabajos activos; espera a que terminen |
| Salida HLS vacía | La salida hls necesita un array ladder con niveles |
status: failed | Lee el campo error; normalmente un origen ilegible, un endpoint S3 inalcanzable o credenciales de salida incorrectas |
Trabajo atascado en queued | Normal con carga alta; la codificación arranca según se libera capacidad, así que observa progress |
Referencia de la API
POST /transcoder/jobs Crear un trabajo. (scope: transcoder:write)
POST /transcoder/jobs/batch Crear hasta 1000 trabajos en una llamada. (scope: transcoder:write)
GET /transcoder/jobs Listar tus trabajos. ?limit ?offset ?batch_id (scope: transcoder:read)
GET /transcoder/jobs/{uuid} Estado y progreso del trabajo. (scope: transcoder:read)
GET /transcoder/jobs/{uuid}/outputs Ubicación de los archivos producidos. (scope: transcoder:read)
DELETE /transcoder/jobs/{uuid} Cancelar un trabajo. (scope: transcoder:write)
Todas las peticiones se autentican con Authorization: Bearer <token> (o X-API-Key). Las peticiones de escritura necesitan además X-Requested-With: XMLHttpRequest.
