Ir al contenido principal

API de PageToVid: entra una URL, sale un MP4

Envía la URL de una página web y recibe un vídeo narrado y subtitulado de ella. Un POST inicia el vídeo y un GET te dice cuándo está listo. Para automatizaciones, generación masiva y vistas previas Open Graph de vídeo.

Crear vídeos con POST /api/v1/videos requiere el plan Pro o superior; en un plan inferior responde 402 con el código UPGRADE_REQUIRED. El servidor MCP funciona en todos los planes, incluido el gratuito.

¿Usas un asistente de IA?Conecta PageToVid a Claude, Cursor, VS Code o Codex y simplemente pide un vídeo.¿Trabajas en VS Code?PageToVid Studio configura el servidor MCP para Claude Code en un clic, con comandos slash para vídeos, series y formatos.

Tu primera llamada en tres pasos

  1. 1. Consigue una clave de API

    Crea una clave API en tu cuenta, y envíala como token Bearer. Mantenla en secreto — gasta tus créditos.

    shell
    export PAGETOVID_API_KEY="cp_live_xxxxxxxxxxxxxxxx"
  2. 2. Inicia un vídeo

    Solo la URL es obligatoria; cada otro campo tiene un valor por defecto. La respuesta llega enseguida, con un id y un statusUrl. Un vídeo terminado cuesta 40 créditos.

    curl
    curl -X POST https://pagetovid.com/api/v1/videos \ -H "Authorization: Bearer $PAGETOVID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-site.com" }'
  3. 3. Recoge el resultado

    Llama al statusUrl cada pocos segundos, o indica un callbackUrl y espera nuestro POST. Cuando status es "done", videoUrl es tu MP4.

    curl
    curl https://pagetovid.com/api/v1/videos/VIDEO_ID \ -H "Authorization: Bearer $PAGETOVID_API_KEY"

URL base: https://pagetovid.com · un vídeo renderizado cuesta 40 créditos.

Crear un vídeo

POST /api/v1/videos — devuelve de inmediato un id; el renderizado se ejecuta async.

curl
curl -X POST https://pagetovid.com/api/v1/videos \ -H "Authorization: Bearer $PAGETOVID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://stripe.com", "goal": "ad", "aspect": "16:9", "language": "en", "targetSeconds": 35, "subtitles": true, "outputs": ["16:9", "9:16"], "qualityGate": { "minScore": 70, "onFail": "hold" }, "pronunciations": [{ "text": "SaaS", "say": "sass" }], "callbackUrl": "https://your-server.com/hooks/pagetovid" }'
  • goal — explainer · ad · demo · tutorial · article
  • aspect — 16:9 · 9:16 · 1:1 · 4:5
  • targetSeconds — la duración en segundos, de 15 a 180 (45 por defecto); la narración se escribe para encajar
  • outputs — opcional: de 1 a 4 formatos a partir de un solo guion gráfico, cada uno con su id
  • qualityGate — opcional: retener un vídeo con nota inferior a minScore en lugar de entregarlo (onFail: auto_fix, hold o deliver)
  • pronunciations — opcional: cómo pronuncia la voz una palabra; los subtítulos mantienen la ortografía
  • callbackUrl — opcional: enviamos aquí un POST firmado cuando termina el render (done, held, error o cancelled) — mira Verifica el webhook más abajo

Respuesta (202):

json
{ "id": "cmq…", "status": "analyzing", "statusUrl": "https://pagetovid.com/api/v1/videos/cmq…", "webhookSecret": "pvwh_…", // only on the first call that sets a callbackUrl: keep it "outputs": [ // only when several shapes were asked for { "aspect": "16:9", "id": "cmq…", "statusUrl": "…" }, { "aspect": "9:16", "id": "cmr…", "statusUrl": "…" } ] }

Verifica el webhook

Cada llamada lleva X-PageToVid-Timestamp, X-PageToVid-Event-Id y X-PageToVid-Signature: sha256= seguido del HMAC-SHA256 en hexadecimal de la marca de tiempo, un punto y el cuerpo bruto con tu secreto de webhook (se devuelve una sola vez como webhookSecret y se sustituye con rotate_webhook_secret). Recalcúlalo sobre el cuerpo bruto, compara en tiempo constante y rechaza una marca de tiempo de más de 300 segundos. El id del evento no cambia entre reintentos: úsalo para ignorar un duplicado.

javascript
import { createHmac, timingSafeEqual } from "node:crypto"; // raw: the request body exactly as received, BEFORE any JSON parsing. function verifyPageToVid(raw, headers, secret) { const ts = headers["x-pagetovid-timestamp"]; const sig = headers["x-pagetovid-signature"] ?? ""; if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const want = "sha256=" + createHmac("sha256", secret).update(ts + "." + raw).digest("hex"); return sig.length === want.length && timingSafeEqual(Buffer.from(sig), Buffer.from(want)); }

Obtener un vídeo / consultar estado

GET /api/v1/videos/:id — consulta hasta status: "done".

json
{ "id": "cmq…", "status": "done", // queued | analyzing | scripting | recording | voicing | rendering | done | held | error "progress": 100, "videoUrl": "https://pagetovid.com/media/cmq…/video.mp4", "posterUrl": "https://pagetovid.com/media/cmq…/scene-2.png", "subtitleUrl": "https://pagetovid.com/media/cmq…/subtitles.vtt", "durationMs": 41000, "width": 1920, "height": 1080, "score": 82, // the reviewer's score, or null "qualityGate": { "minScore": 70, "onFail": "hold", "result": "passed", "held": false }, "og": { "og:video": "…", "og:image": "…", "og:video:type": "video/mp4", … } }

Sitios, plan y programas

El plan del sitio y los programas que un asistente gestiona por el MCP también tienen puertas REST, con la misma clave. Un sitio se identifica por su dominio.

  • GET/POST /api/v1/sites/:domain/plan — el plan de un sitio entero — los vídeos que debería tener, cada uno con objetivo, brief y planos. POST lo genera o regenera (gratis).
  • GET/POST /api/v1/sites/:domain/plan/videos — el conjunto de vídeos que pide el plan, con el vídeo en que ya se convirtió cada idea. POST previsualiza; POST con confirm: true los lanza, 40 créditos cada uno.
  • GET/POST /api/v1/sites/:domain/shows — los programas del sitio — vídeos recurrentes desde un feed RSS, Atom o JSON: el mismo storyboard en cada episodio, datos nuevos en cada episodio. POST crea uno; empieza en espera.
  • POST /api/v1/shows/:id/run — lanza un programa a mano. Sin confirm, previsualiza los episodios gratis; confirm: true los crea (40 créditos cada uno) y, en un programa en espera, lo confirma — el planificador horario lo emite después, dentro de sus límites por emisión y por día. PATCH /api/v1/shows/:id pausa, reanuda o ajusta.

POST /api/v1/sites/example.com/shows

json
{ "name": "Release notes", "kind": "video", "feed_kind": "rss", "feed_url": "https://example.com/changelog.xml", "cadence": "weekly", "max_episodes_per_run": 1, "max_credits_per_day": 80, "aspect_ratio": "9:16", "language": "en", "goal": "explainer", "target_seconds": 30, "focus_template": "This episode is about {{title}}: {{summary}}. Lead with what is new." }

201 { "ok": true, "show": { "show_id", "confirmed": false, … } }, y luego POST /api/v1/shows/:id/run { "confirm": true }. max_credits_per_day limita lo que el programa puede gastar al día: aquí 2 vídeos.

Un programa nunca gasta antes de que su propietario haya visto una emisión, nunca más de max_credits_per_day al día, y nunca crea dos veces el mismo elemento del feed.

Más endpoints

La misma clave Bearer abre estos. Los campos de la petición son los que lee cada ruta; las respuestas se muestran tal como llegan.

  • POST /api/v1/videos/:id/translate — cuerpo { languages: de 1 a 6 códigos de idioma, idempotencyKey? } (o la cabecera Idempotency-Key). Un vídeo nuevo por idioma, 40 créditos cada uno: se conservan el storyboard y las imágenes y se traducen las palabras. 202 → { sourceVideoId, videos: [{ language, ok, video_id, status, credits_charged, … }], creditsCharged }. Requiere el mismo plan que POST /api/v1/videos.
  • PATCH /api/v1/shows/:id — cuerpo { active?, cadence?, max_episodes_per_run?, max_credits_per_day?, name? }: pausa, reanuda o ajusta un programa → { ok: true }.
  • GETPOST /api/v1/sites — GET lista tus sitios: [{ id, domain, name, createdAt }]. POST { url, name? } registra el sitio al que pertenece una URL → 201 { ok, site }.
  • GET /api/v1/sites/:domain — un sitio → { known, domain, name, createdAt, videos, assets, public_videos, shows, formats, session, site_url, channel_url, feed_url, credits }. Un dominio que no has filmado responde { known: false, domain }.
  • GETPUT /api/v1/sites/:domain/brand-kit — el kit de marca del sitio → { ok, domain, exists, kit, logo_url, font_url, film_theme, painted, not_painted, watermark_effective, watermark_from }. PUT es una actualización parcial: una clave enviada reemplaza, null borra, una clave ausente se conserva. Una clave desconocida se rechaza con la lista aceptada; un ajuste que tu plan no incluye responde 402.
  • GETPOSTDELETE /api/v1/sites/:domain/session — la sesión de captura del sitio, que permite grabar detrás de un inicio de sesión. POST { storage_state } (un storage state de Playwright) o { login_url, email, password } de una cuenta de prueba, con origin, label y ttl_hours opcionales → 201 { ok, domain, how, session, origins }; la contraseña nunca se guarda. GET → { ok, domain, sessions, capture_allowed, capture_plan }, nunca las cookies. DELETE olvida todas las sesiones del sitio → { ok, removed }. Por debajo del plan Scale, POST responde 402.
  • POST /api/v1/sites/:domain/record-requests — pide a la extensión de Chrome que grabe los recorridos del sitio. POST { flowIds? } (ninguno = todos los que aún no están grabados) → 201 { ok, request }; una petición nueva sustituye a la pendiente. El navegador la muestra y una persona pulsa Grabar; una petición que nadie toma caduca a los 30 minutos.
  • GETPATCH /api/v1/record-requests — las peticiones de grabación abiertas de quien llama, cada una con sus recorridos, para el navegador: GET → { ok, requests, pollMs }. GET /api/v1/record-requests/:id sigue una (pending, claimed, done, declined o expired, con el resultado por recorrido); PATCH { status: claimed | done | declined, result? } la avanza, 409 si ya cambió.
  • GETPOST /api/v1/characters — identidades que se mantienen coherentes entre clips generados. GET → { ok, characters }. POST { name, refs? (URL de imágenes de esta cuenta), generate? (dibuja una referencia por 40 créditos), description?, aspect?, site_url? } → 201 { ok, character, credits_charged }. DELETE /api/v1/characters/:name → { ok, name }.
  • GETPOST /api/v1/connections — los servidores externos que pueden llamar los resolvers de un formato, por alias. GET → { ok, connections }. POST { alias, kind: mcp | http, url, secret?, note? } → 201 { ok, connection }; un secreto se acepta y nunca se devuelve. DELETE /api/v1/connections/:alias → { ok, alias }.
  • GET /api/v1/commons — la biblioteca compartida, sin clave: ?kind=image|video, tag, q, limit (hasta 200) → { ok, assets: [{ id, url, kind, tags, prompt, attribution, licence, created_at }] }. Poner en un vídeo el recurso generado de otra cuenta pasa por «Usar recurso» en la aplicación, que paga a su autor.
  • GET /api/v1/sites/:domain/formats — los formatos del sitio (la forma versionada que sigue un programa): format_id, name, kind, latest_version, shows, programme, updated_at.
  • POST /api/v1/shows/:id/webhook — sin clave API: envía elementos a un programa cuyo feed_kind es webhook. El cuerpo es items: un array de 1 a 50 objetos, cada uno con un title y, si quieres, key, link, summary, image y date. Firma el cuerpo bruto con el secreto del propio programa: X-PageToVid-Signature es sha256= seguido de su HMAC-SHA256 en hexadecimal. Un programa sin confirmar responde held y no gasta nada.
  • PUT /api/v1/uploads/:token — sin clave API: envía el archivo a la upload_url que devolvió create_upload (curl -T), hasta 30 MB; 413 por encima.

Úsalo como vídeo Open Graph (SEO / social)

Un vídeo terminado también sirve como rica vista previa og:video — tus enlaces se despliegan como un vídeo reproducible en redes y buscadores. La respuesta de estado te da las etiquetas exactas en og. Colócalas en el <head> de tu página:

html
<meta property="og:image" content="POSTER_URL" /> <meta property="og:video" content="VIDEO_URL" /> <meta property="og:video:secure_url" content="VIDEO_URL" /> <meta property="og:video:type" content="video/mp4" /> <meta property="og:video:width" content="1920" /> <meta property="og:video:height" content="1080" /> <meta name="twitter:card" content="player" />

Automatízalo

Genera + espera, en cualquier lenguaje.

javascript
const KEY = process.env.PAGETOVID_API_KEY; const create = await fetch("https://pagetovid.com/api/v1/videos", { method: "POST", headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ url: "https://your-site.com", goal: "ad" }), }).then((r) => r.json()); let v; do { await new Promise((r) => setTimeout(r, 5000)); v = await fetch(create.statusUrl, { headers: { Authorization: `Bearer ${KEY}` } }).then((r) => r.json()); } while (!["done", "held", "error"].includes(v.status)); console.log(v.videoUrl, v.posterUrl, v.og);
python
import os, time, requests KEY = os.environ["PAGETOVID_API_KEY"] H = {"Authorization": f"Bearer {KEY}"} r = requests.post("https://pagetovid.com/api/v1/videos", headers=H, json={"url": "https://your-site.com", "goal": "ad"}).json() while True: time.sleep(5) v = requests.get(r["statusUrl"], headers=H).json() if v["status"] in ("done", "held", "error"): break print(v["videoUrl"], v["posterUrl"], v["og"])

Errores y límites

  • 401 — clave API no válida o ausente.
  • 402 — sin créditos, o la API no está en tu plan (código UPGRADE_REQUIRED). Recarga o mejora tu plan en precios.
  • 400 — entrada no válida (se requiere una url).
  • 404 — no encontrado: el vídeo, el sitio, el programa, el personaje o la conexión no está en esta cuenta.
  • 409 — un conflicto: un render con esta Idempotency-Key aún está arrancando, la clave se usó para otra petición, o el vídeo está ocupado.
  • 422 — entendido pero no hecho: un inicio de sesión que no entró, o una traducción en la que ningún idioma pudo arrancar.
  • 429 — demasiadas peticiones; cuando se envía una cabecera Retry-After, indica cuántos segundos esperar.
  • 500 / 502 — falló algo de nuestro lado, o un servicio al que llamábamos. Un crédito gastado en lo que falló se devuelve; vuelve a intentarlo en un momento.
  • El renderizado es async — tarda unos minutos; consulta la URL de estado.
  • CORS está habilitado en /api/v1/videos (crear, consultar, traducir), así que puedes llamarlos directamente desde el navegador; las demás rutas son para servidores.

Obtén tu clave API

La API REST viene con el plan Pro y superiores, desde 49 $ al mes. En todos los planes, el gratuito incluido, tu asistente de IA crea vídeos por el MCP — empieza por ahí, gratis.