Disclosure: I work on Revana, a pay-as-you-go API for image and video models. The workflow below calls its REST API, but the shape (price first, cap the spend, one idempotency key per job, wait without hammering the API) is what you want around any paid generation API in n8n.
Most "AI video in n8n" tutorials stop at one HTTP Request node that fires a prompt at a model. That works once in a demo. In a real workflow it breaks in three ways:
This post builds a workflow out of stock n8n nodes (HTTP Request, IF, Wait, Stop and Error) that handles all three. No community node is required, though there is one at the end if you prefer it.
Manual Trigger
-> Settings (Set)
-> Quote (HTTP Request, free)
-> Within budget? (IF) --no--> Stop and Error
-> Create generation (HTTP Request, Idempotency-Key)
-> Wait 20 s
-> Get generation (HTTP Request)
-> Succeeded? (IF) --yes--> Download video (HTTP Request, file)
--no---> Failed? (IF) --yes--> Stop and Error
--no---> back to Wait
Every Revana model goes through the same two endpoints, so the workflow is the same for Seedance 2.5, Kling 4.0, Wan 3.0, Veo 3.1 Fast and the rest. Only the model value and its options change.
Create an API key at revana.si/dashboard. In n8n, go to Credentials > Add credential > Header Auth:
Authorization
Bearer sr_live_...
Call it Revana API. On each HTTP Request node below, set Authentication to Generic Credential Type, Generic Auth Type to Header Auth, and pick that credential.
A Set node holds everything you'd want to change per run:
| Field | Example |
|---|---|
model |
seedance-2.5 |
prompt |
a paper boat drifting down a rainy street at night, neon reflections, rain sounds |
resolution |
720p |
duration |
5 |
aspect_ratio |
9:16 |
audio |
true |
budget_usd |
2 |
Valid values differ by model. GET https://api.revana.si/v1/models (no key needed) lists each model's resolutions, durations and aspect ratios, for example Seedance 2.5 takes 4 to 30 seconds and Kling 4.0 takes 3 to 30. A value a model doesn't offer is refused with 422 invalid_input before anything is charged, so a typo costs nothing.
POST /v1/generations/quote takes exactly the body you're about to send and answers what it will cost. It's free: nothing is created, held or charged.
Quote node (HTTP Request):
POST
https://api.revana.si/v1/generations/quote
={{ JSON.stringify({
model: $json.model,
input: {
prompt: $json.prompt,
resolution: $json.resolution,
duration: $json.duration,
aspect_ratio: $json.aspect_ratio,
audio: $json.audio
}
}) }}
The answer has two numbers worth knowing:
quoted_usd: the price on the first route tried. This is what a successful job normally costs.reserved_usd: the most it could cost if the first provider fails and a dearer backup delivers instead. That amount is held from your balance while the job runs.
With the settings above (Seedance 2.5, 720p, 5 s, 9:16, audio), the quote on November 5, 2026 was $1.05 ($0.21 per second), with up to $4.05 reserved if every backup were allowed. The same settings on Kling 4.0 quoted $0.68, on Wan 3.0 $0.35, and Veo 3.1 Fast at 6 seconds (it takes 4, 6 or 8) $0.60. Re-quote rather than copying these: prices change, and some inputs cost more than the model's headline rate. Seedance 2.5 with a first frame quoted $0.25 a second instead of $0.21.
Within budget? (IF node), Number condition:
{{ $json.quoted_usd }} is less than or equal to {{ $('Settings').item.json.budget_usd }}
The false branch goes to a Stop and Error node with a message like Costs ${{ $json.quoted_usd }}, over the budget.
Create generation (HTTP Request):
https://api.revana.si/v1/generations
Idempotency-Key = n8n-{{ $execution.id }}
={{ JSON.stringify({
model: $('Settings').item.json.model,
input: {
prompt: $('Settings').item.json.prompt,
resolution: $('Settings').item.json.resolution,
duration: $('Settings').item.json.duration,
aspect_ratio: $('Settings').item.json.aspect_ratio,
audio: $('Settings').item.json.audio
},
max_cost_usd: $json.quoted_usd
}) }}
Two things make this node safe to retry:
200) instead of starting a second one. Sending the same key with a 409 idempotency_conflict, which is what you want if someone edits the node between attempts. If you loop over many items in one execution, make the key per item, for example n8n-{{ $execution.id }}-{{ $itemIndex }}. max_cost_usd is a hard ceiling.
The response is 202 with a generation object: id, status (queued), quoted_usd, reserved_usd, and cost_usd (null until it finishes).
A 5-second 720p clip usually takes one to a few minutes, sometimes longer. Polling every second wastes executions and can hit the per-second burst guard (429 rate_limited). A loop with a Wait node is enough:
GET https://api.revana.si/v1/generations/{{ $('Create generation').item.json.id }}
{{ $json.status }} equals succeeded → download.failed → Stop and Error with {{ $json.error.code }}: {{ $json.error.message }}. Otherwise connect the false branch back to Two details:
$0 (cost_usd: 0). error.retryable tells you whether sending the same request later can work (model_busy, timeout, all_routes_failed) or whether the request itself has to change ( content_policy, invalid_input, max_cost_exceeded).{{ $runIndex }} is greater than 60 (about 20 minutes at 20 s per lap). $runIndex counts how many times that node has run in this execution.
Download video (HTTP Request):
GET https://api.revana.si/v1/generations/{{ $json.id }}/content
The API redirects to a short-lived file link, and n8n follows it. The video arrives as binary data, ready for a Google Drive, S3, Slack or Write Binary File node. Result files expire, so store them; don't keep the link.
If your n8n is reachable on public HTTPS (n8n Cloud, or self-hosted behind a domain), you can drop the loop. Revana's queue endpoint accepts a completion webhook, and n8n's Wait node can until a URL is called.
{model, input} wrapper, with fal_webhook query parameter:
POST https://api.revana.si/v1/queue/revana/{{ $('Settings').item.json.model }}?fal_webhook={{ encodeURIComponent($execution.resumeUrl) }}
Idempotency-Key: n8n-{{ $execution.id }}
{ "prompt": "...", "resolution": "720p", "duration": 5, "aspect_ratio": "9:16", "audio": true, "max_cost_usd": 1.05 }
GET https://api.revana.si/v1/queue/revana/{model}/requests/{{ $('Create generation').item.json.request_id }}/status, and when it's COMPLETED without an error, download .../requests/{request_id}/content with Response Format: File.
The callback URL must be public HTTPS on port 443 with no redirects, so this won't work against localhost:5678. For local development, keep the polling loop.
minimax-h3-free is a free model in the same catalog (5 to 15 seconds, 480p/720p/768p, sound always on). Set model to it in Settings, and the quote comes back $0.00. The account needs one top-up first (the smallest pack is $10, an anti-abuse rule), but free jobs never spend it. It's a good way to run the whole workflow end to end before you point it at a paid model.
n8n import:workflow --input=seedance-2.5-video-generator.json. n8n-nodes-revana (Settings > Community Nodes > Install, if your instance allows community nodes) wraps this into Nothing here is n8n-specific. Make's HTTP module, Zapier's Webhooks by Zapier, Pipedream or a cron job can run the same sequence: POST /v1/generations/quote, POST /v1/generations with an Idempotency-Key and max_cost_usd, then GET /v1/generations/{id} on an interval and GET /v1/generations/{id}/content at the end. The full API is in the docs and openapi.json, and every model and price is at revana.si/pricing.
Prices and quotes in this post are from Revana's live API on November 5, 2026.