Zum Hauptinhalt springen

PageToVid API: URL rein, MP4 raus

Sende die URL einer Webseite und erhalte ein vertontes Video davon, mit Untertiteln. Ein POST startet den Film, ein GET sagt dir, wann er fertig ist. Für Automatisierungen, Massen-Generierung und Open-Graph-Video-Vorschauen.

Videos mit POST /api/v1/videos zu erstellen braucht den Pro-Plan oder höher; auf einem kleineren Plan antwortet die API mit 402 und dem Code UPGRADE_REQUIRED. Der MCP-Server funktioniert auf jedem Plan, auch auf dem kostenlosen.

Nutzt du einen KI-Assistenten?Verbinde PageToVid mit Claude, Cursor, VS Code oder Codex und frag einfach nach einem Video.Du arbeitest in VS Code?PageToVid Studio richtet den MCP-Server für Claude Code mit einem Klick ein – mit Slash-Befehlen für Videos, Serien und Formate.

Dein erster Aufruf in drei Schritten

  1. 1. Hol dir einen API-Schlüssel

    Erstelle einen API-Schlüssel in deinem Konto, und sende ihn als Bearer-Token. Halte ihn geheim — er verbraucht deine Credits.

    shell
    export PAGETOVID_API_KEY="cp_live_xxxxxxxxxxxxxxxx"
  2. 2. Starte einen Film

    Nur die URL ist Pflicht; jedes andere Feld hat einen Standardwert. Die Antwort kommt sofort, mit einer id und einer statusUrl. Ein fertiger Film kostet 40 Credits.

    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. Hol das Ergebnis ab

    Ruf die statusUrl alle paar Sekunden ab, oder gib eine callbackUrl an und warte auf unseren POST. Steht status auf "done", ist videoUrl dein MP4.

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

Basis-URL: https://pagetovid.com · ein gerendertes Video kostet 40 Credits.

Ein Video erstellen

POST /api/v1/videos — gibt sofort eine id zurück; das Rendern läuft asynchron.

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 — die Länge in Sekunden, 15–180 (Standard 45); der Sprechtext wird passend geschrieben
  • outputs — optional: 1 bis 4 Formate aus einem Storyboard, jedes mit eigener id
  • qualityGate — optional: einen Film unter minScore zurückhalten statt ihn auszuliefern (onFail: auto_fix, hold oder deliver)
  • pronunciations — optional: wie die Stimme ein Wort ausspricht; die Untertitel behalten die Schreibweise
  • callbackUrl — optional: Wir senden hierher einen signierten POST, wenn das Rendern endet (done, held, error oder cancelled) — siehe Webhook prüfen weiter unten

Antwort (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": "…" } ] }

Webhook prüfen

Jeder Aufruf trägt X-PageToVid-Timestamp, X-PageToVid-Event-Id und X-PageToVid-Signature: sha256= gefolgt vom hexadezimalen HMAC-SHA256 aus Zeitstempel, einem Punkt und dem rohen Body mit deinem Webhook-Secret (einmal als webhookSecret zurückgegeben, ersetzt durch rotate_webhook_secret). Berechne ihn über den rohen Body neu, vergleiche in konstanter Zeit und lehne einen Zeitstempel ab, der älter als 300 Sekunden ist. Die Event-ID bleibt über Wiederholungen gleich: Nutze sie, um ein Duplikat zu ignorieren.

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)); }

Video abrufen / Status abfragen

GET /api/v1/videos/:id — frage ab, bis 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", … } }

Websites, Plan und Sendungen

Der Website-Plan und die Sendungen, die ein Assistent über das MCP verwaltet, haben auch REST-Türen mit demselben Schlüssel. Eine Website wird über ihre Domain angesprochen.

  • GET/POST /api/v1/sites/:domain/plan — der Plan für eine ganze Website — die Videos, die sie haben sollte, jedes mit Ziel, Briefing und Einstellungen. POST erzeugt ihn (neu), kostenlos.
  • GET/POST /api/v1/sites/:domain/plan/videos — die Videos, die der Plan verlangt, mit dem Video, das jede Idee schon geworden ist. POST zeigt eine Vorschau; POST mit confirm: true startet sie, je 40 Credits.
  • GET/POST /api/v1/sites/:domain/shows — die Sendungen der Website — wiederkehrende Videos aus einem RSS-, Atom- oder JSON-Feed: jede Folge dasselbe Storyboard, jede Folge frische Daten. POST legt eine an; sie startet wartend.
  • POST /api/v1/shows/:id/run — startet eine Sendung von Hand. Ohne confirm eine kostenlose Vorschau der Folgen; confirm: true erzeugt sie (je 40 Credits) und bestätigt eine wartende Sendung — der stündliche Zeitplan übernimmt danach, innerhalb der Grenzen pro Lauf und pro Tag. PATCH /api/v1/shows/:id pausiert, setzt fort oder stellt ein.

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, … } }, dann POST /api/v1/shows/:id/run { "confirm": true }. max_credits_per_day begrenzt, was die Sendung pro Tag ausgeben darf: hier 2 Videos.

Eine Sendung gibt nie Credits aus, bevor ihr Besitzer einen Lauf gesehen hat, nie mehr als max_credits_per_day am Tag, und macht denselben Feed-Eintrag nie zweimal.

Weitere Endpunkte

Derselbe Bearer-Schlüssel öffnet diese. Die Anfragefelder sind die, die jede Route liest; die Antworten sind so gezeigt, wie sie zurückkommen.

  • POST /api/v1/videos/:id/translate — Body { languages: 1 bis 6 Sprachcodes, idempotencyKey? } (oder der Header Idempotency-Key). Ein neues Video pro Sprache, je 40 Credits: Storyboard und Bilder bleiben, die Worte werden übersetzt. 202 → { sourceVideoId, videos: [{ language, ok, video_id, status, credits_charged, … }], creditsCharged }. Braucht denselben Plan wie POST /api/v1/videos.
  • PATCH /api/v1/shows/:id — Body { active?, cadence?, max_episodes_per_run?, max_credits_per_day?, name? }: pausiert, setzt fort oder justiert eine Sendung → { ok: true }.
  • GETPOST /api/v1/sites — GET listet deine Websites: [{ id, domain, name, createdAt }]. POST { url, name? } registriert die Website, zu der eine URL gehört → 201 { ok, site }.
  • GET /api/v1/sites/:domain — eine Website → { known, domain, name, createdAt, videos, assets, public_videos, shows, formats, session, site_url, channel_url, feed_url, credits }. Eine noch nie gefilmte Domain antwortet { known: false, domain }.
  • GETPUT /api/v1/sites/:domain/brand-kit — das Brand-Kit der Website → { ok, domain, exists, kit, logo_url, font_url, film_theme, painted, not_painted, watermark_effective, watermark_from }. PUT ist ein Teil-Update: ein gesendeter Schlüssel ersetzt, null löscht, ein fehlender bleibt. Ein unbekannter Schlüssel wird mit der Liste der erlaubten abgelehnt; eine Einstellung, die dein Plan nicht enthält, antwortet 402.
  • GETPOSTDELETE /api/v1/sites/:domain/session — die Capture-Session der Website, mit der hinter einem Login gefilmt wird. POST { storage_state } (ein Playwright-Storage-State) oder { login_url, email, password } eines Testkontos, optional origin, label und ttl_hours → 201 { ok, domain, how, session, origins }; das Passwort wird nie gespeichert. GET → { ok, domain, sessions, capture_allowed, capture_plan }, nie die Cookies. DELETE vergisst alle Sessions der Website → { ok, removed }. Unter dem Scale-Plan antwortet POST mit 402.
  • POST /api/v1/sites/:domain/record-requests — bittet die Chrome-Erweiterung, die Abläufe der Website aufzunehmen. POST { flowIds? } (keine = alle noch nicht aufgenommenen) → 201 { ok, request }; eine neue Anfrage ersetzt eine offene. Der Browser zeigt sie, und eine Person drückt auf Aufnehmen; eine Anfrage, die niemand annimmt, verfällt nach 30 Minuten.
  • GETPATCH /api/v1/record-requests — die offenen Aufnahme-Anfragen des Aufrufers, jede mit ihren Abläufen, für den Browser: GET → { ok, requests, pollMs }. GET /api/v1/record-requests/:id verfolgt eine (pending, claimed, done, declined oder expired, mit dem Ergebnis pro Ablauf); PATCH { status: claimed | done | declined, result? } bewegt sie, 409, wenn sie sich schon bewegt hat.
  • GETPOST /api/v1/characters — Identitäten, die über generierte Clips hinweg gleich bleiben. GET → { ok, characters }. POST { name, refs? (Bild-URLs dieses Kontos), generate? (zeichnet eine Referenz für 40 Credits), description?, aspect?, site_url? } → 201 { ok, character, credits_charged }. DELETE /api/v1/characters/:name → { ok, name }.
  • GETPOST /api/v1/connections — die Upstream-Server, die die Resolver eines Formats aufrufen dürfen, per Alias. GET → { ok, connections }. POST { alias, kind: mcp | http, url, secret?, note? } → 201 { ok, connection }; ein Secret wird angenommen und nie zurückgegeben. DELETE /api/v1/connections/:alias → { ok, alias }.
  • GET /api/v1/commons — die geteilte Asset-Bibliothek, ohne Schlüssel: ?kind=image|video, tag, q, limit (bis 200) → { ok, assets: [{ id, url, kind, tags, prompt, attribution, licence, created_at }] }. Ein generiertes Asset eines anderen Kontos kommt über „Asset verwenden“ in der App in einen Film, das bezahlt seinen Urheber.
  • GET /api/v1/sites/:domain/formats — die Formate der Website (die versionierte Form, der eine Sendung folgt): format_id, name, kind, latest_version, shows, programme, updated_at.
  • POST /api/v1/shows/:id/webhook — ohne API-Key: schickt Einträge an eine Sendung mit feed_kind webhook. Der Body ist items: ein Array aus 1–50 Objekten, jedes mit title und optional key, link, summary, image und date. Signiere den rohen Body mit dem eigenen Secret der Sendung: X-PageToVid-Signature ist sha256= gefolgt von seinem hexadezimalen HMAC-SHA256. Eine unbestätigte Sendung antwortet held und gibt nichts aus.
  • PUT /api/v1/uploads/:token — ohne API-Key: schick die Datei an die upload_url, die create_upload zurückgegeben hat (curl -T), bis 30 MB; darüber 413.

Als Open-Graph-Video nutzen (SEO / Social)

Ein fertiges Video dient zugleich als reiche og:video-Vorschau — Links zu deiner Seite erscheinen als abspielbares Video in sozialen Netzwerken und Suchmaschinen. Die Statusantwort liefert dir die genauen Tags in og. Setze sie in den <head> deiner Seite:

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" />

Automatisiere es

Generieren + warten, in jeder Sprache.

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"])

Fehler & Limits

  • 401 — ungültiger oder fehlender API-Schlüssel.
  • 402 — keine Credits mehr, oder die API ist nicht in deinem Tarif (Code UPGRADE_REQUIRED). Aufladen oder upgraden unter Preise.
  • 400 — ungültige Eingabe (eine url ist erforderlich).
  • 404 — nicht gefunden: Video, Website, Sendung, Figur oder Verbindung gehört nicht zu diesem Konto.
  • 409 — ein Konflikt: ein Render mit diesem Idempotency-Key startet noch, der Schlüssel wurde für eine andere Anfrage benutzt, oder das Video ist belegt.
  • 422 — verstanden, aber nicht erledigt: ein Login, der nicht angemeldet hat, oder eine Übersetzung, bei der keine Sprache starten konnte.
  • 429 — zu viele Anfragen; wenn ein Retry-After-Header gesendet wird, nennt er die Sekunden bis zum nächsten Versuch.
  • 500 / 502 — auf unserer Seite ist etwas schiefgegangen, oder bei einem Dienst, den wir aufgerufen haben. Ein für das Fehlgeschlagene ausgegebener Credit kommt zurück; versuche es gleich noch einmal.
  • Das Rendern ist asynchron — es dauert ein paar Minuten; frage die Status-URL ab.
  • CORS ist auf /api/v1/videos (erstellen, abfragen, übersetzen) aktiviert, du kannst diese also direkt aus dem Browser aufrufen; die anderen Endpunkte sind für Server.

Hol dir deinen API-Schlüssel

Die REST-API ist ab dem Pro-Tarif enthalten, ab 49 $ im Monat. In jedem Tarif, auch im kostenlosen, erstellt dein KI-Assistent Videos über das MCP — fang dort kostenlos an.