API PageToVid : une URL en entrée, un MP4 en sortie
Envoyez l'URL d'une page web et recevez une vidéo commentée et sous-titrée de cette page. Un POST lance le film, un GET vous dit quand il est prêt. Pour les automatisations, la génération en masse et les aperçus Open Graph vidéo.
Créer des vidéos avec POST /api/v1/videos demande l'offre Pro ou supérieure ; sur une offre inférieure, la réponse est un 402 avec le code UPGRADE_REQUIRED. Le serveur MCP fonctionne sur toutes les offres, gratuite comprise.
Vous utilisez un assistant IA ?Connectez PageToVid à Claude, Cursor, VS Code ou Codex et demandez simplement une vidéo.Vous travaillez dans VS Code ?PageToVid Studio configure le serveur MCP pour Claude Code en un clic, avec des commandes slash pour les vidéos, les émissions et les formats.Votre premier appel en trois étapes
1. Obtenez une clé d'API
Créez une clé API dans votre compte, puis envoyez-la comme jeton Bearer. Gardez-la secrète — elle consomme vos crédits.
shellexport PAGETOVID_API_KEY="cp_live_xxxxxxxxxxxxxxxx"2. Lancez un film
Seule l'URL est obligatoire ; chaque autre champ a une valeur par défaut. La réponse arrive tout de suite, avec un id et un statusUrl. Un film terminé coûte 40 crédits.
curlcurl -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. Récupérez le résultat
Appelez le statusUrl toutes les quelques secondes, ou indiquez un callbackUrl et attendez notre POST. Quand status vaut "done", videoUrl est votre MP4.
curlcurl https://pagetovid.com/api/v1/videos/VIDEO_ID \ -H "Authorization: Bearer $PAGETOVID_API_KEY"
URL de base : https://pagetovid.com · une vidéo rendue coûte 40 crédits.
Créer une vidéo
POST /api/v1/videos — renvoie immédiatement un id ; le rendu s'exécute en asynchrone.
curlcurl -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 · articleaspect— 16:9 · 9:16 · 1:1 · 4:5targetSeconds— la durée en secondes, de 15 à 180 (45 par défaut) ; la narration est écrite pour tenir dedansoutputs— facultatif : de 1 à 4 formats à partir d'un seul storyboard, chacun avec son idqualityGate— facultatif : retenir un film noté sous minScore au lieu de le livrer (onFail : auto_fix, hold ou deliver)pronunciations— facultatif : comment la voix prononce un mot ; les sous-titres gardent l'orthographecallbackUrl— facultatif : nous y envoyons un POST signé à la fin du rendu (done, held, error ou cancelled) — voir Vérifier le webhook plus bas
Réponse (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": "…" } ] }
Vérifier le webhook
Chaque appel porte X-PageToVid-Timestamp, X-PageToVid-Event-Id et X-PageToVid-Signature : sha256= suivi du HMAC-SHA256 hexadécimal de l'horodatage, d'un point et du corps brut, calculé avec votre secret de webhook (renvoyé une seule fois sous webhookSecret, remplacé par rotate_webhook_secret). Recalculez-le sur le corps brut, comparez en temps constant et refusez un horodatage de plus de 300 secondes. L'identifiant d'événement reste le même d'une tentative à l'autre : servez-vous-en pour ignorer un doublon.
javascriptimport { 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)); }
Récupérer une vidéo / interroger le statut
GET /api/v1/videos/:id — interrogez jusqu'à 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", … } }
Sites, plan et émissions
Le plan de site et les émissions qu'un assistant gère via le MCP existent aussi en REST, avec la même clé. Un site est identifié par son domaine.
- GET/POST
/api/v1/sites/:domain/plan— le plan d'un site entier — les vidéos qu'il devrait avoir, chacune avec un objectif, un brief et ses plans. POST le génère ou le régénère (gratuit). - GET/POST
/api/v1/sites/:domain/plan/videos— l'ensemble des vidéos demandées par le plan, avec la vidéo déjà réalisée pour chaque idée. POST prévisualise ; POST avec confirm: true les lance, 40 crédits chacune. - GET/POST
/api/v1/sites/:domain/shows— les émissions du site — des vidéos récurrentes depuis un flux RSS, Atom ou JSON : même storyboard à chaque épisode, données fraîches à chaque épisode. POST en crée une ; elle démarre en attente. - POST
/api/v1/shows/:id/run— lance une émission à la main. Sans confirm, prévisualisation gratuite des épisodes ; confirm: true les fabrique (40 crédits chacun) et, sur une émission en attente, la confirme — le planificateur horaire prend ensuite le relais, dans ses plafonds par diffusion et par jour. PATCH /api/v1/shows/:id met en pause, reprend ou règle.
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, … } }, puis POST /api/v1/shows/:id/run { "confirm": true }. max_credits_per_day plafonne ce que l'émission peut dépenser par jour : ici 2 vidéos.
Une émission ne dépense jamais avant que son propriétaire ait vu une diffusion, jamais plus que max_credits_per_day par jour, et ne fabrique jamais deux fois le même élément du flux.
Autres endpoints
La même clé Bearer ouvre ceux-ci. Les champs de requête sont ceux que lit chaque route ; les réponses sont montrées telles qu'elles reviennent.
- POST
/api/v1/videos/:id/translate— corps { languages : 1 à 6 codes de langue, idempotencyKey? } (ou l'en-tête Idempotency-Key). Une nouvelle vidéo par langue, 40 crédits chacune : le storyboard et les images sont gardés, les mots traduits. 202 → { sourceVideoId, videos : [{ language, ok, video_id, status, credits_charged, … }], creditsCharged }. Demande la même offre que POST /api/v1/videos. - PATCH
/api/v1/shows/:id— corps { active?, cadence?, max_episodes_per_run?, max_credits_per_day?, name? } : met en pause, relance ou règle une émission → { ok: true }. - GETPOST
/api/v1/sites— GET liste vos sites : [{ id, domain, name, createdAt }]. POST { url, name? } enregistre le site auquel appartient une URL → 201 { ok, site }. - GET
/api/v1/sites/:domain— un site → { known, domain, name, createdAt, videos, assets, public_videos, shows, formats, session, site_url, channel_url, feed_url, credits }. Un domaine jamais filmé répond { known: false, domain }. - GETPUT
/api/v1/sites/:domain/brand-kit— le kit de marque du site → { ok, domain, exists, kit, logo_url, font_url, film_theme, painted, not_painted, watermark_effective, watermark_from }. PUT est une mise à jour partielle : une clé envoyée remplace, null efface, une clé absente est gardée. Une clé inconnue est refusée avec la liste acceptée ; un réglage que votre offre n'inclut pas répond 402. - GETPOSTDELETE
/api/v1/sites/:domain/session— la session de capture du site, qui permet de filmer derrière une connexion. POST { storage_state } (un storage state Playwright) ou { login_url, email, password } d'un compte de test, avec origin, label et ttl_hours facultatifs → 201 { ok, domain, how, session, origins } ; le mot de passe n'est jamais conservé. GET → { ok, domain, sessions, capture_allowed, capture_plan }, jamais les cookies. DELETE oublie toutes les sessions du site → { ok, removed }. Sous l'offre Scale, POST répond 402. - POST
/api/v1/sites/:domain/record-requests— demande à l'extension Chrome d'enregistrer les parcours du site. POST { flowIds? } (aucun = tous ceux pas encore enregistrés) → 201 { ok, request } ; une nouvelle demande remplace celle en attente. Le navigateur l'affiche et une personne appuie sur Enregistrer ; une demande que personne ne prend expire après 30 minutes. - GETPATCH
/api/v1/record-requests— les demandes d'enregistrement ouvertes de l'appelant, chacune avec ses parcours, pour le navigateur : GET → { ok, requests, pollMs }. GET /api/v1/record-requests/:id en suit une (pending, claimed, done, declined ou expired, avec le résultat par parcours) ; PATCH { status: claimed | done | declined, result? } la fait avancer, 409 si elle a déjà bougé. - GETPOST
/api/v1/characters— des identités gardées cohérentes d'un clip généré à l'autre. GET → { ok, characters }. POST { name, refs? (URL d'images de ce compte), generate? (dessine une référence pour 40 crédits), description?, aspect?, site_url? } → 201 { ok, character, credits_charged }. DELETE /api/v1/characters/:name → { ok, name }. - GETPOST
/api/v1/connections— les serveurs amont que les resolvers d'un format peuvent appeler, par alias. GET → { ok, connections }. POST { alias, kind : mcp | http, url, secret?, note? } → 201 { ok, connection } ; un secret est accepté et jamais renvoyé. DELETE /api/v1/connections/:alias → { ok, alias }. - GET
/api/v1/commons— la médiathèque partagée, sans clé : ?kind=image|video, tag, q, limit (jusqu'à 200) → { ok, assets : [{ id, url, kind, tags, prompt, attribution, licence, created_at }] }. Mettre dans un film l'asset généré d'un autre compte passe par « Utiliser l'asset » dans l'application, qui paie son auteur. - GET
/api/v1/sites/:domain/formats— les formats du site (la forme versionnée qu'une émission suit) : format_id, name, kind, latest_version, shows, programme, updated_at. - POST
/api/v1/shows/:id/webhook— sans clé API : pousse des éléments vers une émission dont le feed_kind est webhook. Le corps est items : un tableau de 1 à 50 objets, chacun avec un title et, au choix, key, link, summary, image et date. Signez le corps brut avec le secret propre à l'émission : X-PageToVid-Signature vaut sha256= suivi de son HMAC-SHA256 hexadécimal. Une émission non confirmée répond held et ne dépense rien. - PUT
/api/v1/uploads/:token— sans clé API : envoyez le fichier à l'upload_url renvoyée par create_upload (curl -T), jusqu'à 30 Mo ; 413 au-delà.
Utilisez-la comme vidéo Open Graph (SEO / réseaux sociaux)
Une vidéo finie fait aussi office de riche aperçu og:video — vos liens s'affichent alors comme une vidéo lisible sur les réseaux et dans la recherche. La réponse de statut vous donne les balises exactes dans og. Placez-les dans le <head> de votre page :
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" />
Automatisez
Générez + attendez, dans n'importe quel langage.
javascriptconst 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);
pythonimport 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"])
Erreurs & limites
401— clé API invalide ou manquante.402— plus de crédits, ou l'API n'est pas incluse dans votre offre (code UPGRADE_REQUIRED). Rechargez ou changez d'offre sur tarifs.400— entrée invalide (une url est requise).404— introuvable : la vidéo, le site, l'émission, le personnage ou la connexion n'est pas sur ce compte.409— un conflit : un rendu avec cette Idempotency-Key démarre encore, la clé a servi à une autre requête, ou la vidéo est occupée.422— compris mais pas fait : une connexion qui n'a pas abouti, ou une traduction dont aucune langue n'a pu démarrer.429— trop de requêtes ; quand un en-tête Retry-After est envoyé, il donne le nombre de secondes à attendre.500/502— une erreur de notre côté, ou d'un service que nous appelions. Un crédit dépensé pour ce qui a échoué est rendu ; réessayez dans un instant.- Le rendu est asynchrone — il prend quelques minutes ; interrogez l'URL de statut.
- Le CORS est activé sur /api/v1/videos (création, suivi, traduction), que vous pouvez donc appeler directement depuis le navigateur ; les autres portes sont destinées aux serveurs.
Obtenez votre clé API
L'API REST est incluse dès l'offre Pro, à partir de 49 $ par mois. Sur toutes les offres, gratuite comprise, votre assistant IA crée des vidéos via le MCP — commencez par là, gratuitement.