API Documentation

Complete reference for the Viffly.app REST API

Authentication

The API supports two authentication methods. Both require aBusinessplan or higher for external calls.

Method 1: API key (recommended)
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"
Method 2: Bearer Token
# Obtenir un token via /api/login
curl -X POST https://viffly.app/api/login \
  -H "Content-Type: application/json" \
  -d '{"email":"vous@email.com","password":"..."}'

# Réponse : { "token": "eyJhbG..." }

# Utiliser le token
curl -H "Authorization: Bearer eyJhbG..." \
  https://viffly.app/api/me
Bearer Tokens expire after7 days. API keys (vf_...) have a configurable lifetime and are recommended for production.

Credit system

Each AI operation consumes credits (fixed cost for standard features, per-duration for AI video). Free operations (Pexels, Whisper) consume nothing.

🎬
Video render
5 cr.
📝
AI script
1 cr.
🎙️
AI voice
4 cr.
🖼️
AI image (×1)
2 cr.
🎵
AI music
5 cr.
💡
Image prompts
Free
📷
Pexels images
Free
💬
Transcription
Free
Examples:Budget video (your audio + Pexels) =5 cr.| Standard video (script + AI voice) =10 cr.| Full-AI video (fully automatic) =21 cr.
GET/api/credits/costs

Returns the credit cost of each operation, the available plans and usage scenarios.

Response
{
  "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": 150 }, ...
  ],
  "scenarios": [ { "name": "Budget", "credits": 5 }, ... ]
}
GET/api/meAuth

Returns the account information, including the credit balance.

Response
{
  "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, ... }
}

AI video generation (LTX 2.3 / Wan 2.2)

Generates a video fromtext(t2v) or from animage(i2v) via the AI models. This is the API to use from n8n. The flow isasynchronous: you submit a job, then poll its status untildone.

⚡ LTX 2.3 (default)
Fast (~30-60s/clip), MP4 + native audio.model: "ltx"
🎬 Wan 2.2
More cinematic (~3-5 min/clip), ×2 credits.model: "wan22"
Requires aProplan or higher. Authentication viaX-API-Key.
GET/api/ltx/pricing

Lists the tiers (resolution × duration), aspect ratios and scenarios. No auth required — useful to populate a UI or pick a tier_id.

Response
{
  "tiers": [
    { "id": "tier1", "label": "480p · 5s",  "resolution": "480p",  "seconds": 5,  "credits": 5 },
    { "id": "tier2", "label": "720p · 5s",  "resolution": "720p",  "seconds": 5,  "credits": 10 },
    { "id": "tier3", "label": "720p · 10s", "resolution": "720p",  "seconds": 10, "credits": 15 },
    { "id": "tier4", "label": "720p · 15s", "resolution": "720p",  "seconds": 15, "credits": 22 },
    { "id": "tier5", "label": "720p · 20s", "resolution": "720p",  "seconds": 20, "credits": 30 },
    { "id": "tier6", "label": "1080p · 10s","resolution": "1080p", "seconds": 10, "credits": 32 },
    { "id": "tier7", "label": "1080p · 15s","resolution": "1080p", "seconds": 15, "credits": 48 },
    { "id": "tier8", "label": "1080p · 20s","resolution": "1080p", "seconds": 20, "credits": 65 }
  ],
  "aspects": [ { "id": "9:16" }, { "id": "1:1" }, { "id": "16:9" } ],
  "scenarios": [ { "id": "cinematic" }, { "id": "product" }, ... ],
  "default_negative": "ugly, blurry, low quality, ..."
}
POST/api/ltx/generate10 cr.Auth

Submits a video generation job. Returns a job_id immediately (non-blocking). JSON body.

Request body
FieldTypeDescription
mode*string"t2v" (text→video) or "i2v" (image→video)
prompt*stringVideo description (≥ 3 characters)
tier_idstringtier1…tier8 (resolution × duration). Default: tier2
modelstring"ltx" (default, fast) or "wan22" (cinematic, ×2 cr.)
image_base64stringStarting image in base64 (required if mode=i2v). Without the data: prefix
image_urlstringAlternative: public HTTPS URL of the starting image (i2v)
negativestringNegative prompt (default: see /api/ltx/pricing)
aspectstring"9:16" (default), "1:1", "16:9"
scenario_typestringCreative direction (see /api/ltx/pricing)
fpsnumberFrames/second (default: 24)
seednumberSeed (reproducibility). Random if omitted
audio_onbooleanKeep the generated audio track (default: true)
Response
{
  "job_id": "915c93ea-58b7-4808-a83f-5dfa98ee1ef5",
  "status": "processing",
  "tier": { "id": "tier2", "label": "720p · 5s", "credits": 10 }
}
Example — text → video:
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": "ltx",
    "prompt": "a sports car drifting on a coastal road at sunset, cinematic",
    "tier_id": "tier2",
    "aspect": "9:16"
  }'
Example — image → video:
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": "wan22",
    "prompt": "slow cinematic push in, gentle motion",
    "tier_id": "tier2",
    "image_base64": "'"$(base64 -w0 photo.jpg)"'"
  }'
POST/api/ltx/generate-sync10 cr.Auth

SYNCHRONOUS version: submits, waits for the render internally, then returns the video — in ONE single call (no wait/poll loop). Ideal for a single n8n node. Set a long timeout (e.g. 1200000 ms).

Request body
FieldTypeDescription
(same as /api/ltx/generate)*jsonmode, prompt, tier_id, model, image_base64/image_url, aspect…
Response
// 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":"ltx","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"}'
GET/api/ltx/status/:idAuth

(Async mode) Polls the state of a job. Call it in a loop (every ~5-10 s) until status = done or error. Not needed if you use /generate-sync.

Response
// 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_urlisrelative. Full download URL =https://viffly.app+output_url.

n8n integration — HTTP Request node

The video flow is asynchronous. In n8n, the pattern is:SubmitWaitCheck the status→ loop while ≠done.

① HTTP Request (POST /generate)② Wait 10s③ HTTP Request (GET /status)④ IF status = done?→ no: back to ②→ yes: download
n8n node builder

Configure the options below then generate the HTTP Request node JSON — copy it and paste it directly into the n8n canvas (Ctrl+V), or download the file.

Or configure the nodes manually:
NODE 1Submit the job — HTTP Request
MethodPOST
URLhttps://viffly.app/api/ltx/generate
AuthenticationNone (we pass the key in the header below)
Send HeadersON → Name: X-API-Key | Value: vf_votre_cle
Send BodyON → Body Content Type: JSON
Specify BodyUsing JSON
Body (JSON)
{
  "mode": "i2v",
  "model": "ltx",
  "prompt": "{{ $json.prompt }}",
  "tier_id": "tier2",
  "aspect": "9:16",
  "image_base64": "{{ $binary.data.data }}"
}

i2v: if the image comes from a previous node (e.g. Telegram, Read Binary File), reference its base64 binary data with{{ $binary.data.data }}. For t2v, removeimage_base64and set"mode": "t2v".

NODE 2 + 3Wait then check the status

Node 2 — Wait: 10 seconds.Node 3 — HTTP Request(poll):

MethodGET
URLhttps://viffly.app/api/ltx/status/{{ $('NODE 1').item.json.job_id }}
Send HeadersON → X-API-Key: vf_votre_cle
NODE 4Loop — IF status = done?

NodeIF— condition (String):{{ $json.status }} equals done.

  • false(not ready yet) → connect the output toNode 2 (Wait): the loop continues.
  • true(done) → continue. The final URL =https://viffly.app{{ $json.output_url }}
Download the video (HTTP Request)
MethodGET
URLhttps://viffly.app{{ $json.output_url }}
Response FormatFile / Binary
n8n tips
  • To avoid an infinite loop: add a counter (Set node + IF on a max number of iterations, e.g. 60 ≈ 10 min).
  • Wan 2.2 takes 3-5 min: set the Wait to 15-20 s to reduce the number of calls.
  • The cost is charged once at launch andautomatically refundedif the render fails.
  • No incoming webhook: it's polling. An API keyvf_…("API" tab) is enough.

Reel generation (audio + images)

POST/api/generate-sync5 cr.Auth

Generates a video and waits for the result (20 min timeout). Returns the download URL.

Request body
FieldTypeDescription
audio*fileMP3 audio file of the narration
imagesfile[]Images (1 to 4 files). Alternative: image_url
image_urlstringURL of an image (alternative to images)
image_url_0..NstringIndexed image URLs, no longer capped at 3 (up to 30 media). An .mp4/.mov URL here plays as a video clip at that position.
mediastring[]Ordered JSON array mixing images AND videos — array order = display order. Videos detected by extension (.mp4, .mov, .webm…).
formatstringOutput format: 9:16 (default), 16:9, 1:1, 4:5, 4:3
titlestringTitle shown in the intro
sourcestringSource / site name
script_textstringScript text for the subtitles
templatestring32 templates : default, bold, clean, news, minimal, highlight, cinematic, neon, retro, gaming, story, meme, comic, glitch, noir, vaporwave, duotone, matrix, ticker…
caption_stylestringkaraoke, glow, outline, neon, block, shadow, gradient, typewriter
caption_sizestringS, M, L, XL
caption_positionstringtop, center, bottom
languagestringfr, en, ar, es, de, it, pt
profilestringRender profile name (e.g. shadesplays)
music_urlstringBackground music URL (loops automatically when the video outlasts the track)
music_start_atnumberMusic start offset (seconds)
voice_typestring"ai" for ElevenLabs narration (+4 cr.)
voice_idstringElevenLabs voice ID
outro_textstringEnd screen text
outro_substringEnd screen subtitle
Response
{
  "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
}
Full example:
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"
POST/api/generate5 cr.Auth

Starts the generation in the background. Returns a jobId to track progress via SSE.

Response
{ "jobId": "e9e2d4f9-..." }
Track progress:GET /api/videos/events?jobId=...(Server-Sent Events)

Videos

GET/api/videosAuth

Lists all the account's videos, sorted by descending date.

Response
[
  {
    "id": "7865aac1-...",
    "title": "Mon titre",
    "status": "done",
    "url": "https://...",
    "thumbnail": "https://...",
    "size": 8542190,
    "duration": 27.5,
    "created": "2026-03-28T..."
  }, ...
]
GET/api/videos/:id.mp4Auth

Download a video's MP4 file.

DELETE/api/videos/:idAuth

Delete a video and its associated files.

AI services

POST/api/ai/script1 cr.Auth

Generates a narration script optimized for virality.

Request body
FieldTypeDescription
prompt*stringTopic or instruction
existingstringExisting script to improve
contextstringAdditional context (web content)
languagestringTarget language (fr, en, ar...)
Response
{ "script": "Saviez-vous que...", "charsUsed": 342, "charsMax": 700 }
POST/api/generate-images2 cr.Auth

Generates images via FAL Flux 2 Pro.

Request body
FieldTypeDescription
prompt*stringImage description
countnumberNumber of images (1-3, default: 3)
Response
{ "images": ["https://fal.media/...", ...] }
Cost: 2 credits × number of images
POST/api/ai/image-promptAuth

Generates 3 image prompts optimized for a given topic.

Request body
FieldTypeDescription
topic*stringVideo topic
stylestringDesired visual style
Response
{ "prompts": ["A cinematic...", "An aerial...", "A close-up..."] }
POST/api/generate-music5 cr.Auth

Generates background music via Suno AI.

Request body
FieldTypeDescription
promptstringDescription of the musical style
stylestringMusic genre (pop, electronic, cinematic...)
instrumentalbooleanInstrumental only (default: true)
titlestringTrack title
Response
{ "taskId": "abc123", "status": "pending" }
Check the status:GET /api/generate-music/:taskId
GET/api/elevenlabs/voicesAuth

Lists the available ElevenLabs voices (1h cache).

Response
{ "voices": [{ "voice_id": "pNInz6ob...", "name": "Adam", "labels": {...} }, ...] }

API key management

GET/api/keysAuth

Lists all your API keys.

Response
[{
  "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
}]
POST/api/keysAuth

Creates a new API key. The full key is only returned at creation.

Request body
FieldTypeDescription
namestringKey name (e.g. Production)
scopesstring[]Permissions: generate, videos, credits, ai_script, ai_images, ai_music
rate_limitnumberRequests per minute (1-1000, default: 60)
expires_in_daysnumberDays before expiration (null = never)
Response
{
  "id": "uuid",
  "key": "vf_a1b2c3d4e5f6...",  // ⚠️ Affiché UNE SEULE FOIS
  "name": "Production",
  "scopes": ["generate", "videos", "credits"]
}
PATCH/api/keys/:idAuth

Edit a key (name, permissions, rate limit, enable/disable).

Request body
FieldTypeDescription
namestringNew name
is_activebooleanEnable/disable
scopesstring[]New permissions
rate_limitnumberNew rate limit
DELETE/api/keys/:idAuth

Permanently deletes an API key.

Media library

GET/api/musicAuth

Lists the music library (uploaded tracks + Suno).

GET/api/pexels/imagesAuth

Pexels image search.

Request body
FieldTypeDescription
qquerySearch term
pagequeryPage (default: 1)
per_pagequeryResults per page (default: 20)
GET/api/pexels/videosAuth

Pexels video search.

Error codes

CodeMeaningAction
401Invalid or expired tokenRenew the token or create a new API key
402Insufficient creditsBuy a pack or upgrade to a higher plan
403Insufficient plan (Business+ required)Upgrade to Business or Agency
429Rate limit exceededWait 1 minute or increase the rate limit
500Server errorRetry or contact support
Standard error format
{
  "error": "Message descriptif de l'erreur",
  "creditsNeeded": 5  // (optionnel) crédits nécessaires
}
Rate limiting

The default limits are:

  • API keys: configurable per key (1-1000 req/min, default 60)
  • Bearer Token: 100 req/min global
  • AI endpoints (script, images, music): 10 req/min
  • Video generation: 3 concurrent max per account