L'API supporte deux méthodes d'authentification. Les deux nécessitent un plan Business ou supérieur pour les appels externes.
curl -X POST https://viffly.app/api/generate-sync \ -H "X-API-Key: vf_votre_cle_api" \ -F "audio=@narration.mp3" \ -F "title=Ma vidéo"
# Obtenir un token via /api/login
# session : "short" (12 h) | "default" (7 j) | "long" (30 j)
curl -X POST https://viffly.app/api/login \
-H "Content-Type: application/json" \
-d '{"email":"vous@email.com","password":"...","session":"short"}'
# Réponse : { "token": "eyJhbG...", "expires_at": "...", "session_ttl": "12h" }
# Utiliser le token
curl -H "Authorization: Bearer eyJhbG..." \
https://viffly.app/api/mevf_...) sont scopées, révocables, renouvelables et limitées en débit individuellement : c'est la méthode à utiliser en production.Chaque clé porte une liste de scopes. Un appel vers un endpoint non couvert est refusé avec un 403 nommant le scope manquant — accordez le minimum nécessaire.
| Scope | Endpoints couverts |
|---|---|
| generate | /api/ltx/* · /api/generate · /api/generate-sync · talking-head · restyle · montage · longshort · loop-assemble · director · slideshow · reframe · autocut · audio-clean · watermark · voiceover · media-jobs |
| ai_images | /api/image-jobs · /api/generate-images · /api/images/edit · /api/characters/* |
| ai_script | /api/ai/* |
| videos | /api/videos |
| credits | /api/me · /api/credits/* |
Une clé sans scope enregistré (créée avant l'introduction des scopes) passe partout. Renouvelez-la pour lui appliquer une portée.
# Renouveler le secret d'une clé (nom, scopes et limites conservés)
curl -X POST https://viffly.app/api/keys/{id}/rotate \
-H "Authorization: Bearer eyJhbG..."
# Déconnecter tous les autres appareils
curl -X POST https://viffly.app/api/account/revoke-sessions \
-H "Authorization: Bearer eyJhbG..."Chaque opération IA consomme des crédits (coût fixe pour les fonctions classiques, à la durée pour la vidéo IA). Les opérations gratuites (Pexels, Whisper) ne consomment rien.
/api/credits/costsRetourne le coût en crédits de chaque opération, les plans disponibles et des scénarios d'utilisation.
{
"costs": { "render": 5, "ai_script": 1, "ai_voice": 4, "ai_image": 2, "ai_music": 5, "pexels": 0 },
"packages": [
{ "slug": "free", "label": "Free", "monthlyCredits": 15 },
{ "slug": "pro", "label": "Pro", "monthlyCredits": 200 }, ...
],
"scenarios": [ { "name": "Budget", "credits": 5 }, ... ]
}/api/meAuthRetourne les informations du compte, incluant le solde de crédits.
{
"id": "abc123",
"client_email": "vous@email.com",
"package": "pro",
"creditsTotal": 142,
"creditsMonthly": 130,
"creditsPack": 12,
"monthlyAllowance": 150,
"packageLabel": "Pro",
"creditCosts": { "render": 5, "ai_voice": 4, ... }
}Génère une vidéo à partir d'un texte (t2v) ou d'une image (i2v) via les modèles IA. C'est l'API à utiliser depuis n8n. Le flux est asynchrone : on soumet un job, puis on interroge son statut jusqu'à done.
model: "hailuo23"model: "wan27"X-API-Key./api/ltx/pricingListe les tiers (résolution × durée), les ratios d'aspect et les scénarios. Aucune auth requise — utile pour peupler une UI ou choisir un tier_id.
{
"tiers": [
{ "id": "tier1", "label": "720p · 5s", "resolution": "720p", "seconds": 5, "credits": 10 },
{ "id": "tier2", "label": "720p · 5s", "resolution": "720p", "seconds": 5, "credits": 10 },
{ "id": "tier3", "label": "720p · 10s", "resolution": "720p", "seconds": 10, "credits": 20 },
{ "id": "tier6", "label": "1080p · 5s", "resolution": "1080p", "seconds": 5, "credits": 10 },
{ "id": "tier7", "label": "1080p · 10s","resolution": "1080p", "seconds": 10, "credits": 20 }
],
"models": [
{ "id": "wan27", "provider": "dashscope_wan", "tiers": [...] },
{ "id": "hailuo23", "provider": "minimax_hailuo", "tiers": [...] }
],
"aspects": [ { "id": "9:16" }, { "id": "1:1" }, { "id": "16:9" } ],
"scenarios": [ { "id": "cinematic" }, { "id": "product" }, ... ],
"default_negative": "ugly, blurry, low quality, ..."
}/api/ltx/generate10 cr.AuthSoumet un job de génération vidéo. Retourne immédiatement un job_id (ne bloque pas). Body en JSON.
| Champ | Type | Description |
|---|---|---|
| mode* | string | "t2v" (texte→vidéo) ou "i2v" (image→vidéo) |
| prompt* | string | Description de la vidéo (≥ 3 caractères) |
| tier_id | string | tier1, tier2, tier3, tier6 ou tier7. Consultez /pricing pour les combinaisons du moteur. |
| model | string | "hailuo23" (MiniMax), "happyhorse" ou "wan27" (Alibaba) |
| image_base64 | string | Image de départ en base64 (requis si mode=i2v). Sans préfixe data: |
| image_url | string | Alternative: URL publique HTTPS de l'image de départ (i2v) |
| negative | string | Prompt négatif (défaut: voir /api/ltx/pricing) |
| aspect | string | "9:16" (défaut), "1:1", "16:9" |
| scenario_type | string | Direction créative (voir /api/ltx/pricing) |
| fps | number | Images/seconde (défaut: 24) |
| seed | number | Graine (reproductibilité). Aléatoire si omis |
| audio_on | boolean | Garder la piste audio générée (défaut: true) |
{
"job_id": "915c93ea-58b7-4808-a83f-5dfa98ee1ef5",
"status": "processing",
"tier": { "id": "tier2", "label": "720p · 5s", "credits": 10 }
}curl -X POST https://viffly.app/api/ltx/generate \
-H "X-API-Key: vf_votre_cle" \
-H "Content-Type: application/json" \
-d '{
"mode": "t2v",
"model": "hailuo23",
"prompt": "a sports car drifting on a coastal road at sunset, cinematic",
"tier_id": "tier2",
"aspect": "9:16"
}'curl -X POST https://viffly.app/api/ltx/generate \
-H "X-API-Key: vf_votre_cle" \
-H "Content-Type: application/json" \
-d '{
"mode": "i2v",
"model": "wan27",
"prompt": "slow cinematic push in, gentle motion",
"tier_id": "tier2",
"image_base64": "'"$(base64 -w0 photo.jpg)"'"
}'/api/ltx/generate-sync10 cr.AuthVersion SYNCHRONE : soumet, attend le rendu en interne, puis renvoie la vidéo — en UN seul appel (pas de boucle wait/poll). Idéal pour un node n8n unique. Mets un timeout long (ex. 1200000 ms).
| Champ | Type | Description |
|---|---|---|
| (identique à /api/ltx/generate)* | json | mode, prompt, tier_id, model, image_base64/image_url, aspect… |
// Par défaut → renvoie le FICHIER MP4 (binaire). Dans n8n : Response Format = File.
// Avec ?format=json → renvoie l'URL à la place :
{
"status": "done",
"job_id": "915c93ea-...",
"output_url": "/videos/ltx_915c93ea-....mp4",
"url": "https://viffly.app/videos/ltx_915c93ea-....mp4"
}# Récupère directement le MP4 (sortie binaire)
curl -X POST https://viffly.app/api/ltx/generate-sync \
-H "X-API-Key: vf_votre_cle" -H "Content-Type: application/json" \
-d '{"mode":"t2v","model":"hailuo23","prompt":"...","tier_id":"tier2"}' \
--max-time 1200 -o video.mp4
# Ou récupère juste l'URL (JSON)
curl -X POST "https://viffly.app/api/ltx/generate-sync?format=json" \
-H "X-API-Key: vf_votre_cle" -H "Content-Type: application/json" \
-d '{"mode":"t2v","prompt":"...","tier_id":"tier2"}'/api/ltx/status/:idAuth(Mode async) Interroge l'état d'un job. À appeler en boucle (toutes les ~5-10 s) jusqu'à status = done ou error. Inutile si tu utilises /generate-sync.
// en cours
{ "id": "915c93ea-...", "status": "processing", "progress": 50 }
// terminé
{
"id": "915c93ea-...",
"status": "done",
"output_url": "/videos/ltx_915c93ea-....mp4", // relatif → préfixer https://viffly.app
"progress": 100
}
// échec (crédits remboursés)
{ "id": "915c93ea-...", "status": "error", "error": "..." }output_url est relatif. URL de téléchargement complète = https://viffly.app + output_url.Transforme une vidéo horizontale, verticale ou carrée en miniatures multi-formats. Choisis des frames réelles, des frames retouchées par IA ou de nouveaux visuels générés depuis le sujet et la transcription.
/api/thumbnails5 cr.AuthLance un job depuis une URL vidéo, une source S3 autorisée ou un fichier déjà hébergé par Viffly.
| Champ | Type | Description |
|---|---|---|
| source_url* | string | URL HTTPS publique, s3:// autorisé ou /videos/... |
| count | number | 1 à 6 variantes par format (défaut : 3) |
| formats | string[] | auto, 16:9, 9:16, 1:1, 4:5 |
| visual_mode | string | frames (défaut), enhance ou generate |
| ai_quality | string | standard (2 cr./image) ou premium (5 cr./image) |
| image_prompt | string | Direction facultative pour la retouche ou la génération |
| language | string | Langue du titre automatique : auto, fr, en, ar, es, de ou pt |
| title | string | Titre optionnel ; généré depuis le contenu si omis |
| subtitle | string | Sous-titre optionnel |
| style | string | editorial, bold, clean, minimal |
| tone | string | auto, warm, cool, mono |
| smart | boolean | Analyse visuelle + transcription + point focal |
| include_text | boolean | Ajouter ou non la composition typographique |
| accent | string | Couleur hexadécimale, ex. #6D4AFF |
{
"job_id": "53a9c34d-...",
"status": "processing",
"cost": 11,
"visual_mode": "generate",
"cost_breakdown": { "composition": 5, "ai_images": 6 },
"outputs_planned": 3,
"status_url": "/api/thumbnails/53a9c34d-..."
}curl -X POST https://viffly.app/api/thumbnails \
-H "X-API-Key: vf_votre_cle" \
-H "Content-Type: application/json" \
-d '{
"source_url": "https://cdn.exemple.com/video.mp4",
"count": 3,
"formats": ["16:9", "9:16"],
"visual_mode": "generate",
"ai_quality": "standard",
"language": "fr",
"image_prompt": "documentary editorial visual, authentic texture, no text",
"style": "editorial",
"smart": true
}'/api/thumbnails/upload5 cr.AuthUpload multipart pratique pour n8n et les fichiers jusqu’à 200 Mo.
| Champ | Type | Description |
|---|---|---|
| video* | file | MP4, MOV, M4V, MKV, WebM, AVI ou MPEG · 200 Mo max |
| formats | string | Tableau JSON ou liste séparée par des virgules |
| count | number | 1 à 6 variantes par format |
| visual_mode | string | frames, enhance ou generate |
| ai_quality | string | standard ou premium |
| image_prompt | string | Direction facultative pour l’IA |
| language | string | Langue du titre automatique : auto, fr, en, ar, es, de ou pt |
| style | string | editorial, bold, clean, minimal |
{ "job_id": "53a9c34d-...", "status": "processing", "status_url": "/api/thumbnails/53a9c34d-..." }curl -X POST https://viffly.app/api/thumbnails/upload \ -H "X-API-Key: vf_votre_cle" \ -F "video=@episode.mp4" \ -F 'formats=["16:9","9:16"]' \ -F "count=3" \ -F "visual_mode=enhance" \ -F "ai_quality=standard" \ -F "language=fr" \ -F "image_prompt=cinematic light, cleaner background" \ -F "style=editorial"
/api/thumbnails/:idAuthSuit le job et retourne l’analyse, les frames source et toutes les miniatures finales.
{
"job_id": "53a9c34d-...",
"status": "done",
"progress": 100,
"analysis": { "summary": "..." },
"visual_mode": "enhance",
"outputs": [
{ "url": "/videos/thumb_....jpg", "format": "16:9", "source_kind": "enhanced", "master_url": "/videos/mmedit_....png", "raw_frame_url": "/videos/thumb_..._frame_1.jpg", "width": 1280, "height": 720, "timestamp": 42.1, "score": 91 }
]
}Chaîne recommandée : HTTP Request POST → Wait 3 s → HTTP Request GET → IF status = done. Boucle sur Wait tant que le statut est processing.
GET https://viffly.app/api/thumbnails/{{ $('Viffly · Miniatures').item.json.job_id }}Ajoutez callback_url à n'importe quelle création de travail : au lieu d'interroger le statut en boucle, Viffly vous envoie un POST dès que le rendu est terminé. Votre exécution n8n ou Make ne reste jamais bloquée pendant plusieurs minutes.
POST /api/ltx/generate
{
"mode": "i2v",
"prompt": "…",
"callback_url": "https://votre-domaine.com/viffly-hook"
}L'adresse doit être en HTTPS et publiquement joignable. Les adresses privées, localhost et le HTTP simple sont refusés à la création, avant tout débit de crédits.
{
"event": "job.completed", // ou "job.failed"
"job_id": "3f2a…",
"kind": "video",
"status": "done", // ou "error"
"output_url": "/videos/ltx_3f2a….mp4",
"error": null,
"delivered_at": "2026-07-27T19:44:12.031Z"
}X-Viffly-Eventnom de l'événement, identique au champ event du corpsX-Viffly-Deliveryidentifiant unique de la livraison — servez-vous-en pour ignorer un doublonX-Viffly-Timestamphorodatage unix en secondes, entrant dans le calcul de la signatureX-Viffly-Signaturesha256=… — HMAC du corps, permet de vérifier que l'appel vient bien de nousCalculez le HMAC-SHA256 de « horodatage.corps » avec votre secret et comparez-le à l'en-tête. Utilisez le corps BRUT de la requête : re-sérialiser le JSON change les espaces et invalide la comparaison.
const crypto = require('crypto');
function verifie(req, secret) {
const ts = req.headers['x-viffly-timestamp'];
const recu = req.headers['x-viffly-signature'];
const attendu = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(ts + '.' + req.rawBody) // corps BRUT, non re-sérialisé
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(recu), Buffer.from(attendu));
}En cas d'échec, jusqu'à 4 tentatives espacées (immédiate, +2 s, +15 s, +60 s). Une réponse 4xx est considérée comme un refus définitif et n'est pas retentée. Renvoyez un 2xx dès réception.
Le flux vidéo est asynchrone. Dans n8n, le pattern est : Soumettre → Attendre → Vérifier le statut → boucler tant que ≠ done.
Configure les options ci-dessous puis génère le JSON du node HTTP Request — copie-le et colle-le directement dans le canvas n8n (Ctrl+V), ou télécharge le fichier.
| Method | POST |
| URL | https://viffly.app/api/ltx/generate |
| Authentication | None (on passe la clé en header ci-dessous) |
| Send Headers | ON → Name: X-API-Key | Value: vf_votre_cle |
| Send Body | ON → Body Content Type: JSON |
| Specify Body | Using JSON |
{
"mode": "i2v",
"model": "hailuo23",
"prompt": "{{ $json.prompt }}",
"tier_id": "tier2",
"aspect": "9:16",
"image_base64": "{{ $binary.data.data }}"
}i2v : si l'image vient d'un node précédent (ex. Telegram, Read Binary File), référence sa donnée binaire base64 avec {{ $binary.data.data }}. Pour t2v, retire image_base64 et mets "mode": "t2v".
Node 2 — Wait : 10 secondes. Node 3 — HTTP Request (poll) :
| Method | GET |
| URL | https://viffly.app/api/ltx/status/{{ $('NODE 1').item.json.job_id }} |
| Send Headers | ON → X-API-Key: vf_votre_cle |
Node IF — condition (String) : {{ $json.status }} equals done.
https://viffly.app{{ $json.output_url }}| Method | GET |
| URL | https://viffly.app{{ $json.output_url }} |
| Response Format | File / Binary |
vf_… (onglet « API ») suffit./api/generate-sync5 cr.AuthGénère une vidéo et attend le résultat (timeout 20 min). Retourne l'URL de téléchargement.
| Champ | Type | Description |
|---|---|---|
| audio* | file | Fichier audio MP3 de la narration |
| images | file[] | Images (1 à 4 fichiers). Alternative : image_url |
| image_url | string | URL d'une image (alternative à images) |
| image_url_0..N | string | URLs d'images indexées, sans limite de 3 (jusqu'à 30 médias). Une URL .mp4/.mov placée ici joue comme clip vidéo à cette position. |
| media | string[] | Tableau JSON ordonné mêlant images ET vidéos — l'ordre du tableau = l'ordre d'affichage. Vidéos détectées par extension (.mp4, .mov, .webm…). |
| format | string | Format de sortie : 9:16 (défaut), 16:9, 1:1, 4:5, 4:3 |
| title | string | Titre affiché dans l'intro |
| source | string | Source / nom du site |
| script_text | string | Texte du script pour les sous-titres |
| template | string | 32 templates : default, bold, clean, news, minimal, highlight, cinematic, neon, retro, gaming, story, meme, comic, glitch, noir, vaporwave, duotone, matrix, ticker… |
| caption_style | string | karaoke, glow, outline, neon, block, shadow, gradient, typewriter |
| caption_size | string | S, M, L, XL |
| caption_position | string | top, center, bottom |
| language | string | fr, en, ar, es, de, it, pt |
| profile | string | Nom du profil de rendu (ex: shadesplays) |
| music_url | string | URL de musique de fond (boucle automatiquement si la vidéo est plus longue que la piste) |
| music_start_at | number | Offset de démarrage musique (secondes) |
| voice_type | string | "ai" pour narration MiniMax (+4 cr.) |
| voice_id | string | ID de voix MiniMax |
| outro_text | string | Texte de l'écran de fin |
| outro_sub | string | Sous-titre de l'écran de fin |
{
"videoId": "7865aac1-c301-4060-bd6e-6ca4fe3f6859",
"url": "https://s3.eu-west-3.amazonaws.com/.../render.mp4",
"thumbnail": "https://s3.eu-west-3.amazonaws.com/.../thumb.jpg",
"duration": 27.5,
"size": 8542190
}curl -X POST https://viffly.app/api/generate-sync \ -H "X-API-Key: vf_votre_cle" \ -F "audio=@narration.mp3" \ -F "format=16:9" \ -F 'media=["https://cdn.exemple.com/clip.mp4","https://cdn.exemple.com/photo1.jpg","https://cdn.exemple.com/photo2.jpg"]' \ -F "title=Mon titre" \ -F "source=MonSite.com" \ -F "template=gaming" \ -F "caption_style=karaoke" \ -F "caption_size=L" \ -F "script_text=Voici le texte des sous-titres" \ -F "profile=shadesplays"
/api/generate5 cr.AuthDémarre la génération en arrière-plan. Retourne un jobId pour suivre la progression via SSE.
{ "jobId": "e9e2d4f9-..." }GET /api/videos/events?jobId=... (Server-Sent Events)/api/videosAuthListe toutes les vidéos du compte, triées par date décroissante.
[
{
"id": "7865aac1-...",
"title": "Mon titre",
"status": "done",
"url": "https://...",
"thumbnail": "https://...",
"size": 8542190,
"duration": 27.5,
"created": "2026-03-28T..."
}, ...
]/api/videos/:id.mp4AuthTélécharger le fichier MP4 d'une vidéo.
/api/videos/:idAuthSupprimer une vidéo et ses fichiers associés.
/api/ai/script1 cr.AuthGénère un script de narration optimisé pour la viralité.
| Champ | Type | Description |
|---|---|---|
| prompt* | string | Sujet ou instruction |
| existing | string | Script existant à améliorer |
| context | string | Contexte supplémentaire (contenu web) |
| language | string | Langue cible (fr, en, ar...) |
{ "script": "Saviez-vous que...", "charsUsed": 342, "charsMax": 700 }/api/generate-images2 cr.AuthGénère des images via FAL Flux 2 Pro.
| Champ | Type | Description |
|---|---|---|
| prompt* | string | Description de l'image |
| count | number | Nombre d'images (1-3, défaut: 3) |
{ "images": ["https://fal.media/...", ...] }/api/ai/image-promptAuthGénère 3 prompts d'images optimisés pour un sujet donné.
| Champ | Type | Description |
|---|---|---|
| topic* | string | Sujet de la vidéo |
| style | string | Style visuel souhaité |
{ "prompts": ["A cinematic...", "An aerial...", "A close-up..."] }/api/generate-music5 cr.AuthGénère une musique de fond via MiniMax Music.
| Champ | Type | Description |
|---|---|---|
| prompt | string | Description du style musical |
| style | string | Genre musical (pop, electronic, cinematic...) |
| instrumental | boolean | Instrumental uniquement (défaut: true) |
| title | string | Titre du morceau |
{ "taskId": "abc123", "status": "pending" }GET /api/generate-music/:taskId/api/voicesAuthListe les voix MiniMax disponibles (cache 1h).
{ "voices": [{ "voice_id": "pNInz6ob...", "name": "Adam", "labels": {...} }, ...] }/api/keysAuthListe toutes vos clés API.
[{
"id": "uuid", "name": "Production", "key_prefix": "vf_a1b2c3d4",
"scopes": ["generate","videos"], "rate_limit": 60,
"expires_at": null, "last_used_at": "2026-03-28T...",
"usage_count": 42, "is_active": true
}]/api/keysAuthCrée une nouvelle clé API. La clé complète n'est retournée qu'à la création.
| Champ | Type | Description |
|---|---|---|
| name | string | Nom de la clé (ex: Production) |
| scopes | string[] | Permissions: generate, videos, credits, ai_script, ai_images, ai_music |
| rate_limit | number | Requêtes par minute (1-1000, défaut: 60) |
| expires_in_days | number | Jours avant expiration (null = jamais) |
{
"id": "uuid",
"key": "vf_a1b2c3d4e5f6...", // ⚠️ Affiché UNE SEULE FOIS
"name": "Production",
"scopes": ["generate", "videos", "credits"]
}/api/keys/:idAuthModifier une clé (nom, permissions, rate limit, activer/désactiver).
| Champ | Type | Description |
|---|---|---|
| name | string | Nouveau nom |
| is_active | boolean | Activer/désactiver |
| scopes | string[] | Nouvelles permissions |
| rate_limit | number | Nouveau rate limit |
/api/keys/:idAuthSupprime définitivement une clé API.
/api/musicAuthListe la bibliothèque musicale (pistes uploadées + MiniMax).
/api/pexels/imagesAuthRecherche d'images Pexels.
| Champ | Type | Description |
|---|---|---|
| q | query | Terme de recherche |
| page | query | Page (défaut: 1) |
| per_page | query | Résultats par page (défaut: 20) |
/api/pexels/videosAuthRecherche de vidéos Pexels.
| Code | Signification | Action |
|---|---|---|
| 401 | Token invalide ou expiré | Renouveler le token ou créer une nouvelle clé API |
| 402 | Crédits insuffisants | Acheter un pack ou passer au plan supérieur |
| 403 | Plan insuffisant (Business+ requis) | Upgrader vers Business ou Agency |
| 429 | Rate limit dépassé | Attendre 1 minute ou augmenter le rate limit |
| 500 | Erreur serveur | Réessayer ou contacter le support |
{
"error": "Message descriptif de l'erreur",
"creditsNeeded": 5 // (optionnel) crédits nécessaires
}Les limites par défaut sont :