Cinestoria Entrar

API de Cinestoria

Crea vídeos desde un tema o un guion, sigue su producción, aprueba el guion y descarga el resultado desde tu propio sistema, Zapier, Make o n8n. Todo en JSON sobre HTTPS, en https://cinestoria.com/api/v1. Está en los planes Canal y Estudio.

Los vídeos se producen con las claves de proveedores que tengas conectadas en Cinestoria y respetan tu plan, tu tope de gasto y tus canales, igual que en la web.

Autenticación

Crea una clave en API y webhooks y mándala en cada petición. Se enseña una sola vez: guárdala como un secreto. Si se filtra, anúlala y crea otra.

Authorization: Bearer cin_…

Comprueba que funciona:

curl https://cinestoria.com/api/v1/me -H "Authorization: Bearer cin_…"

Límites

  • 120 peticiones por minuto con cada clave.
  • 30 vídeos nuevos por hora en cada cuenta.
  • 20 lotes por hora en cada cuenta (cada uno, hasta 30 vídeos).
  • Al pasarte, la respuesta es 429 con la cabecera Retry-After (segundos).

Crear un vídeo

POST/api/v1/videos

Manda topic (Cinestoria investiga y escribe el guion) o script (tu guion), no los dos. Responde 201 con el vídeo. Con la cabecera Idempotency-Key, si repites la misma petición (por ejemplo, un reintento de Zapier) te devuelve el vídeo que ya creó en lugar de hacer otro.

curl -X POST https://cinestoria.com/api/v1/videos \
  -H "Authorization: Bearer cin_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1234" \
  -d '{"channel_id": 1, "topic": "El faro que nadie podía construir",
       "minutes": 6, "quality": "estandar", "review": false}'
CampoQué es
channel_idObligatorio. Uno de tus canales (GET /api/v1/channels). El vídeo usa su voz, su estilo y su memoria.
topicEl tema (hasta 1.000 caracteres).
scriptTu guion completo (al menos un par de párrafos).
format16:9, 9:16, short. Por defecto, el del canal. short es un Short vertical de menos de un minuto.
minutesDuración aproximada (1–30) cuando mandas un tema.
qualityeconomico, estandar, premium. Por defecto, la del canal.
reviewtrue (por defecto): se para cuando el guion está listo para que lo revises (evento video.review) antes de gastar en voz e imágenes. false: sigue hasta el final.
storyboardBocetos baratos para revisar antes de las imágenes buenas. Por defecto, lo que tenga el canal.
subtitlesSubtítulos en la imagen (true por defecto en vertical y Shorts; false en horizontal). El .srt se entrega siempre.
soundSonido ambiente (true por defecto).
musicai, tension, calma, epica, none. Vacío: la del canal.
languagees, en, pt, fr, de, it. Vacío: el del canal.
genredocumental, empresas, sabiduria, espiritual, terror, ilustrada, relato.
narrationvoz (con narrador) o texto (sin narrador: la historia se lee en pantalla, pensado para relatos). Vacío: la del canal.
personRelatos: primera (lo cuenta el protagonista) o segunda («tú» lo vives). Vacío: la del canal.
graphics, archive, clipsGrafismo (ninguno, ligero, normal, intenso), imágenes reales de archivo (ninguno, algo, mucho) y clips de vídeo (none, hook, chapters, medio, maximo, ratio, key, all).

Crear varios vídeos de una vez

POST/api/v1/videos/batch

Los mismos campos que un vídeo, pero con topics: una lista de temas (hasta 30). Se crea un vídeo por tema, todos con los mismos ajustes. Antes de crear ninguno se comprueba el lote entero (tu plan, tu tope de gasto para todos): o se crean todos o ninguno. Responde 201 con data, la lista de vídeos. Con Idempotency-Key, repetir la petición devuelve el mismo lote.

Con "skip_similar": true no se crean los temas casi iguales a un vídeo que el canal ya tiene (o a otro tema del lote): la respuesta los lista en skipped. Si se saltan todos, 400 con el código all_similar.

curl -X POST https://cinestoria.com/api/v1/videos/batch \
  -H "Authorization: Bearer cin_…" \
  -H "Content-Type: application/json" \
  -d '{"channel_id": 1, "minutes": 8, "quality": "estandar",
       "topics": ["El faro que nadie podía construir", "El error de unidades que hizo perder una sonda en Marte"]}'

Todas las rutas

GET/api/v1/me

Tu cuenta, tu plan y el nombre de la clave.

GET/api/v1/channels

Tus canales con su idioma, formato, calidad y duración por defecto.

GET/api/v1/videos

Tus vídeos, del más nuevo al más antiguo. Filtros: state, channel_id, limit (hasta 100) y before_id para pasar de página (usa next_before_id de la respuesta).

POST/api/v1/videos/batch

Varios vídeos de una vez, uno por tema (cómo).

GET/api/v1/videos/{id}

Un vídeo, con su progreso y, si se ha subido, sus subidas a YouTube.

GET/api/v1/videos/{id}/script

El guion, escena a escena (para revisarlo en tu sistema).

POST/api/v1/videos/{id}/approve

Aprueba el guion o el storyboard tal cual, como el botón de la web. Queda apuntado que lo aprobó la API con esa clave. Si el vídeo no espera revisión: 409.

POST/api/v1/videos/{id}/derivados

Escribe, a partir del guion de un vídeo terminado, un artículo para el blog, un hilo para X, un post de LinkedIn, un texto de Instagram, una newsletter y una publicación de comunidad (unos céntimos en tu cuenta de Anthropic). Responde 202 y tarda alrededor de un minuto.

GET/api/v1/videos/{id}/derivados

Su estado (none, running, done o error) y, cuando están, las piezas en pieces: articulo (titulo, meta, markdown), hilo, linkedin, instagram, newsletter y comunidad.

GET/api/v1/videos/{id}/files/{nombre}

Descarga un archivo de un vídeo terminado: video.mp4, miniatura.jpg, subtitulos.srt, descripcion.txt, short_1.mp4… Las rutas completas vienen en files y shorts del vídeo. Los archivos se borran por antigüedad (expired).

El objeto vídeo

{
  "id": 42, "kind": "video", "parent_id": null, "channel_id": 1,
  "state": "done", "state_label": "Terminado",
  "title": "El faro que nadie podía construir", "topic": "El faro…", "format": "16:9",
  "created": "2026-10-28T09:12:03Z", "finished": "2026-10-28T09:41:55Z",
  "progress": 100, "stage": null, "error": null, "cost_eur": 3.12, "expired": false,
  "files": {"video": "https://cinestoria.com/api/v1/videos/42/files/video.mp4",
            "thumbnail": "https://cinestoria.com/api/v1/videos/42/files/miniatura.jpg",
            "subtitles": "https://cinestoria.com/api/v1/videos/42/files/subtitulos.srt"},
  "shorts": [],
  "metadata": {"title": "…", "description": "…", "tags": ["faros", "ingeniería"],
               "chapters": [{"start": 0.0, "title": "La roca"}, {"start": 74.5, "title": "El faro"}],
               "duration": 362.4},
  "review": {"score": 9.5, "warnings": ["sonoridad"], "frames": []},
  "app_url": "https://cinestoria.com/app/videos/42"
}

review es la revisión final del vídeo terminado: la nota sobre 10, las comprobaciones que avisan y los fotogramas con algún problema (segundo y tipo). Es null hasta que está hecha.

stateQué significa
queuedEn cola
runningEn producción
reviewEsperando la revisión del guion
storyboardEsperando la revisión del storyboard
doneTerminado
errorError
stoppedDetenido

Webhooks

En lugar de preguntar cada poco, añade en API y webhooks la URL que debe recibir los avisos. Cinestoria le hace un POST con JSON cuando pasa algo:

EventoCuándo
video.reviewGuion listo para revisar
video.storyboardStoryboard listo para revisar
video.doneVídeo terminado
video.errorError en un vídeo
youtube.uploadedSubido a YouTube (publicado, programado o privado)
pingAl pulsar «Probar»
POST https://tu-servidor.com/cinestoria
Content-Type: application/json
Cinestoria-Event: video.done
Cinestoria-Delivery: evt_1234
Cinestoria-Signature: t=1793100000,v1=5f1c…

{"id": "evt_1234", "type": "video.done", "created": 1793100000,
 "data": { …el objeto vídeo… }}
  • Responde con un código 2xx en menos de 10 segundos. Si no, se reintenta al cabo de 1 min, 5 min, 30 min, 2 h y 6 h.
  • Tras 20 fallos seguidos el webhook se pausa y te avisamos por email. Lo reanudas con un botón.
  • Un mismo aviso puede llegar dos veces: usa id para no procesarlo dos veces.
  • Solo URLs https a direcciones públicas, sin redirecciones.

Comprobar la firma

Cada webhook tiene su secreto (whsec_…), que ves una vez al crearlo. La cabecera Cinestoria-Signature lleva t (segundos) y v1: el HMAC-SHA256, en hexadecimal, de "{t}.{cuerpo}" con ese secreto. Calcúlalo con el cuerpo tal cual llega (sin volver a formatear el JSON), compáralo en tiempo constante y rechaza los avisos de hace más de 5 minutos.

# Python
import hashlib, hmac, time

def valida(cuerpo: bytes, cabecera: str, secreto: str) -> bool:
    partes = dict(p.split("=", 1) for p in cabecera.split(","))
    t, firma = partes["t"], partes["v1"]
    esperada = hmac.new(secreto.encode(), f"{t}.".encode() + cuerpo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperada, firma) and abs(time.time() - int(t)) < 300
// Node.js
const crypto = require("crypto");

function valida(cuerpo, cabecera, secreto) {
  const p = Object.fromEntries(cabecera.split(",").map(x => x.split("=")));
  const esperada = crypto.createHmac("sha256", secreto).update(`${p.t}.${cuerpo}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(p.v1))
    && Math.abs(Date.now() / 1000 - Number(p.t)) < 300;
}

Errores

Los errores llegan con su código HTTP y un JSON con un code estable y un message para personas:

{"error": {"code": "invalid_channel", "message": "channel_id tiene que ser uno de tus canales…"}}
HTTPcodeQué pasa
401missing_key, invalid_keyFalta la clave, no existe o está anulada.
402blockedTu cuenta no puede producir ahora (pago pendiente, email sin confirmar…).
403planTu plan no incluye la API.
404not_found, no_scriptEse vídeo o archivo no existe en tu cuenta.
400invalid_json, invalid_request, invalid_channel, invalid_state, rejectedLa petición no vale; message dice por qué (por ejemplo, el tope de gasto).
409missing_keys, not_waitingFaltan claves de proveedores en Conexiones, o el vídeo no espera revisión.
429rate_limitedDemasiadas peticiones: espera lo que diga Retry-After.

Términos · Privacidad