Subtitula cualquier vídeo
con tres llamadas API
Envía la URL de un vídeo y recibe un mp4 con subtítulos incrustados palabra por palabra. Whisper ajusta cada palabra, una pasada de IA elige las que se resaltan y el render corre en nuestra flota de workers.
POSThttps://autostud.ai/api/v1/actions/subtitles.generateTodo el recorrido, en un solo archivo
Pégalo, pon tu clave, ejecútalo. Aquí no hay pseudocódigo.
const API = 'https://autostud.ai/api/v1'
const KEY = process.env.AUTOSTUD_API_KEY
const call = async (path, body) => {
const response = await fetch(API + path, {
method: body ? 'POST' : 'GET',
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const payload = await response.json()
if (!response.ok) throw new Error(payload.error?.message || response.statusText)
return payload
}
// 1. A video project to hang the timeline on.
const created = await call('/actions/videos.create', {
video_name: 'Interview clip',
video_type_id: 'timeline',
video_format: 'portrait',
video_lang: 'en',
})
const video_id = created.data.video_id
// 2. Transcribe, emphasise, style — one call.
const subtitled = await call('/actions/subtitles.generate', {
video_id,
media_url: 'https://cdn.example.com/interview.mp4',
style_preset: 'beast',
words_per_group: 3,
})
console.log(subtitled.data.word_count, 'words', subtitled.data.duration_seconds, 's')
// 3. Render it.
await call('/actions/renders.create', {
video_id,
video_type_id: 'timeline',
video_format: 'portrait',
})
// 4. Wait for the worker. A webhook is better; this is the short version.
let render
do {
await new Promise((resolve) => setTimeout(resolve, 15000))
const list = await call(`/renders?video_id=${video_id}&limit=1`)
render = list.data[0]
} while (render && ['not_started', 'processing'].includes(render.render_status))
if (render.render_status !== 'done') throw new Error('Render failed')
console.log(render.render_url)URL base: https://autostud.ai/api/v1 — autentica con `Authorization: Bearer sk_live_…`. Una clave `sk_test_` recorre exactamente el mismo camino y se detiene antes de gastar nada.
Lo que realmente tienes que hacer
Seis pasos, cuatro de ellos una sola petición.
- 1
Crear una clave API
Panel, Ajustes, Claves API. Elige el preajuste «Automation» si no quieres seleccionar scopes a mano: cubre vídeos, renders y archivos. El secreto se muestra una vez. `sk_live_` gasta, `sk_test_` valida toda la petición y se detiene antes de escribir.
export AUTOSTUD_API_KEY="sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # What can this key actually do? curl https://autostud.ai/api/v1 \ -H "Authorization: Bearer $AUTOSTUD_API_KEY" - 2
Poner el vídeo tras una URL
No hay endpoint de subida: la API lee una URL. Tu bucket, tu CDN, un enlace firmado — cualquier cosa que nuestros servidores puedan descargar. Registrarlo en la biblioteca del espacio es opcional y no cambia el flujo.
# The API takes a URL, never a file upload. Anything publicly # reachable works: your bucket, your CDN, a signed URL. export MEDIA_URL="https://cdn.example.com/interview.mp4" # Optional: keep a record of it in the workspace library. curl -X POST https://autostud.ai/api/v1/assets \ -H "Authorization: Bearer $AUTOSTUD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider_file_url": "'"$MEDIA_URL"'", "provider_file_name": "interview.mp4", "file_mime_type": "video/mp4", "provider_file_id": "interview-2026-08-18", "sync_method": "api" }' - 3
Crear el proyecto de vídeo
`videos.create` devuelve el `video_id` del que cuelga todo lo demás. Envía un `Idempotency-Key` y una petición reintentada devuelve la primera respuesta en lugar de crear un segundo proyecto.
curl -X POST https://autostud.ai/api/v1/actions/videos.create \ -H "Authorization: Bearer $AUTOSTUD_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: interview-2026-08-18" \ -d '{ "video_name": "Interview clip", "video_type_id": "timeline", "video_format": "portrait", "video_lang": "en" }' - 4
Subtitularlo
`subtitles.generate` hace todo el trabajo en el servidor: Whisper transcribe con tiempos por palabra, una pasada de IA marca las palabras a resaltar y se aplica uno de los 116 estilos. Responde con el número de palabras, la duración exacta y el idioma detectado.
export VIDEO_ID="9f0c4e2a-1d6b-4a77-9d51-6b0f2e8c3a4d" curl -X POST https://autostud.ai/api/v1/actions/subtitles.generate \ -H "Authorization: Bearer $AUTOSTUD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_id": "'"$VIDEO_ID"'", "media_url": "'"$MEDIA_URL"'", "style_preset": "beast", "words_per_group": 3, "enable_emphasis": true, "enable_diarization": false }' - 5
Encolar el render
`renders.create` pone el trabajo en la flota de workers y responde al momento con el `render_id`. Un render cuesta 20 créditos, sea cual sea la duración.
curl -X POST https://autostud.ai/api/v1/actions/renders.create \ -H "Authorization: Bearer $AUTOSTUD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_id": "'"$VIDEO_ID"'", "video_type_id": "timeline", "video_format": "portrait" }' - 6
Recoger el mp4
Suscríbete a `render.completed` y la URL final llega firmada. Si prefieres consultar tú, lista los renders del vídeo y lee `render_status` hasta que diga `done`.
# Register once, then stop polling. curl -X POST https://autostud.ai/api/v1/webhooks \ -H "Authorization: Bearer $AUTOSTUD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/hooks/autostud", "events": ["render.completed", "render.failed"], "description": "Subtitled videos" }' # The signing secret comes back once, in this response. Store it.
Todas las llamadas de esta página
Los scopes son lo que debe llevar la clave, no lo que envías.
| Llamada | Scope | Qué hace |
|---|---|---|
| GET /api/v1 | - | Descubrimiento: qué puede hacer la clave, sus límites y su consumo. |
| POST /api/v1/actions/videos.create | videos:write | Crea el proyecto de vídeo y devuelve su identificador. |
| POST /api/v1/actions/subtitles.generate | videos:execute | Transcribe, resalta, aplica estilo y guarda la pista de subtítulos. |
| POST /api/v1/actions/renders.create | renders:execute | Encola el render en la flota de workers. 20 créditos. |
| GET /api/v1/renders?video_id=… | renders:read | Los renders de un vídeo, del más reciente al más antiguo, con estado y URL. |
| POST /api/v1/webhooks | webhooks:write | Registra un endpoint https y devuelve su secreto de firma, una vez. |
Estilos disponibles
Pasa uno en `style_preset`. Cada uno fija a la vez la tipografía, el contorno, el color de acento y la animación — no hay nada más que configurar.
default4 palabras · simplebold3 palabras · simpleimpact3 palabras · focus_on_one_wordbeast1 palabras · focus_on_one_wordkaraoke5 palabras · focus_on_one_wordkaraoke_fill5 palabras · karaokeneon4 palabras · progressively_visibleminimal6 palabras · simplepop5 palabras · progressively_visiblepill5 palabras · simplecinematic9 palabras · simpleLo que conviene saber antes de programar
- Los límites de peticiones son por clave y vienen en cabeceras: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After` en un 429.
- `Idempotency-Key` en cualquier POST hace seguro un reintento durante 24 horas; la primera respuesta se repite tal cual.
- Una clave `sk_test_` ejercita autenticación, scopes, límites y validación, y luego responde `simulated: true` sin escribir ni gastar.
- Los errores son tipados: `invalid_request`, `insufficient_credits`, `rate_limit_exceeded`, `upstream_error` — ramifica por el código, nunca por el mensaje.
Cuando falla
Todos los fallos tienen la misma forma: un `code` estable sobre el que ramificar, un `hint` que nombra la llamada que lo arregla y un `request_id` que citar. El vocabulario completo está en /docs/api/errors.
| Código | Qué significa |
|---|---|
| 401 missing_credentials | No hay clave en la petición, o no la reconocemos. |
| 403 insufficient_scope | A la clave le falta un scope; `details.required_scopes` lo nombra. |
| 422 validation_failed | Algún campo es inválido; `details.issues` los lista todos. |
| 402 insufficient_credits | No hay créditos suficientes para el render. Recarga, o usa una clave de prueba. |
| 429 rate_limit_exceeded | Demasiadas llamadas. Espera `Retry-After` segundos y reintenta. |
| 502 upstream_error | Un proveedor del que dependemos ha fallado. Reintenta con backoff. |
{
"error": {
"type": "permission_error",
"code": "insufficient_scope",
"message": "This API key is missing the required scope: videos:execute.",
"details": {
"required_scopes": ["videos:execute"],
"granted_scopes": ["videos:read", "videos:write"]
},
"hint": "`details.required_scopes` lists what is missing. Call GET /api/v1 to see what this key does hold, then re-scope it at /app/settings/api-keys.",
"retryable": false,
"request_id": "req_8f2c41d0a95b",
"doc_url": "https://autostud.ai/docs/api/errors#insufficient_scope"
}
}Deja de hacer polling
Un render tarda minutos. Deja que llegue solo.
Firmados y verificables en diez líneas
`X-Autostud-Signature: t=<unix>,v1=<hex>` es un HMAC-SHA256 sobre `"<timestamp>.<cuerpo crudo>"`. Verifica contra el cuerpo crudo, antes de parsearlo.
Reintentados por ti
Cinco intentos a lo largo de unas dos horas, con la fila de entrega escrita antes del primero. Un endpoint que falla veinte veces seguidas se desactiva en vez de machacarse.
Deduplica por el id de entrega
Cada intento lleva `X-Autostud-Delivery`. Mismo id, mismo evento: trátalo como la clave primaria del trabajo que dispares.
import crypto from 'node:crypto'
// The header is "t=<unix>,v1=<hex>" and the signed string is "<t>.<raw body>".
// Raw body: parse it AFTER verifying, never re-serialize before.
export function verify(raw_body, header, secret, tolerance = 300) {
const parts = Object.fromEntries(
String(header || '').split(',').map((entry) => entry.split('=').map((s) => s.trim()))
)
const timestamp = Number(parts.t)
if (!Number.isFinite(timestamp)) return false
if (Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${raw_body}`)
.digest('hex')
const received = String(parts.v1 || '')
if (received.length !== expected.length) return false
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}
app.post('/hooks/autostud', express.raw({ type: '*/*' }), (req, res) => {
const raw = req.body.toString('utf8')
if (!verify(raw, req.get('X-Autostud-Signature'), process.env.AUTOSTUD_WEBHOOK_SECRET)) {
return res.status(400).send('bad signature')
}
// Deliveries can repeat: deduplicate on this id before doing any work.
const delivery_id = req.get('X-Autostud-Delivery')
const event = JSON.parse(raw)
if (event.event === 'render.completed') {
console.log(event.data.render_url)
}
res.status(200).send('ok')
})Por qué es una llamada y no quince
Las piezas difíciles de rehacer son las que operamos nosotros.
Ajuste por palabra
Cada palabra lleva su inicio y su final, así que el resaltado cae en la sílaba. El ajuste por frase es lo que hace que casi todos los subtítulos automáticos parezcan tardíos.
116 estilos, una cadena
Del subtítulo sobrio de televisión al estilo saltarín resaltado. El preajuste fija tipografía, contorno, color y animación juntos, escalados a tu lienzo.
La IA elige los acentos
Una segunda pasada lee la transcripción y marca las palabras que llevan el sentido. Las muletillas se quedan quietas. Un booleano la desactiva.
Dos voces, dos colores
La diarización es un flag. Una entrevista vuelve con cada voz en su color, y el espectador sabe quién habla sin sonido.
Los mismos objetos que el panel
Un vídeo subtitulado por la API se abre en el editor como cualquier otro. Corrige una palabra a mano, vuelve a renderizar, sigue automatizando: los dos caminos escriben el mismo documento.
Pensada para reintentos
Claves de idempotencia, errores tipados, claves de prueba, webhooks firmados y un registro de peticiones de 30 días. La mitad aburrida de una API, que es la que se nota a las 3 de la mañana.
Las preguntas que de verdad nos llegan
No, y es a propósito: la API toma una URL. Aloja el vídeo donde nuestros servidores puedan leerlo — tu bucket, un CDN, un enlace firmado — y pásalo en `media_url`. Los archivos subidos desde el panel ya tienen una URL utilizable.
Hoy no. Los subtítulos se incrustan en el mp4, que es lo que piden las plataformas de formato corto. La lista de palabras con sus tiempos queda guardada en el vídeo, así que un archivo aparte es una transformación que puedes hacer tú con lo que devuelve la API.
Un render son 20 créditos, fijos, dure lo que dure. La transcripción y la pasada de énfasis se registran en tu espacio como cualquier otra llamada de IA, y `GET /api/v1/usage` informa de lo que ha gastado una clave en el periodo.
Minutos, no segundos: corre en una flota de workers, no dentro de la petición. Justo para eso existe `render.completed` — suscríbete en lugar de mantener una conexión abierta.
Sí. Llama otra vez a `subtitles.generate` con `restyle_only: true` y otro `style_preset`: reutiliza las palabras ya guardadas en el vídeo, así que nada se transcribe dos veces.
Whisper detecta el idioma por su cuenta y los subtítulos salen en el que se habla — no hay que declarar nada. El idioma detectado viene en la respuesta por si quieres ramificar.
Más herramientas gratis
Todas funcionan en el navegador, sin cuenta para probarlas.
Generador de Vídeo Quiz TikTok
Herramienta en vivoConvierte cualquier tema en un vídeo quiz
Generador de Vídeo Quiz YouTube
Herramienta en vivoVídeos quiz en 16:9 o en Shorts
Generador de Vídeo Quiz de Idiomas
Herramienta en vivoConvierte una lista de palabras en un vídeo de idiomas
Tweet a Video
Herramienta en vivoConvierte tweets en videos
YouTube a Vídeo Karaoke
Herramienta en vivoConvierte un tema en vídeo karaoke
Post de Reddit a Vídeo
Herramienta en vivoConvierte un hilo de Reddit en un short
CSV a Vídeo Ranking
Herramienta en vivoConvierte una hoja de cálculo en un vídeo de ranking
Comentarios TikTok a Vídeo Canción
Herramienta en vivoConvierte los comentarios de TikTok en un vídeo canción
Vídeos IA Frutas
Crea vídeos de frutas virales con IA
Publica vídeo subtitulado desde tu propio backend
Crea una clave, ejecuta el quickstart y el primer mp4 subtitulado está a unos minutos.