Search for ai video generation workflow and almost every page you land on is about driving a creative tool: connect your models, arrange your scenes, export your cut. This page is about the other one — the pipeline you build when video generation is a feature inside your product, and the part you own is not the prompt but the queue.
The generation call is the one piece that already works. Everything that breaks after the first successful request sits around it — an input that expires, a task you have to be told about, a delivery that is not retried, a run size your own account decides, and a bill you cannot map back to a task. If you have not made your first call yet, start with the REST API guide instead; the rest of this page assumes a task ID has come back at least once.
Where a video generation workflow actually breaks
The failures are boring and scheduled: a reference image that stops resolving a week later, mid-run, because uploads are retained for about seven days; a callback that arrives while the receiver is mid-deploy and is never retried; a loop that fires three hundred submissions, gets throttled somewhere in the forties, and exits without knowing which ones landed; an invoice with a total and no per-task mapping.
The creative stages — script, storyboard, shot list, voiceover, review loop — are the part this SERP mostly writes about, and they matter here only as counted inputs. What changes at volume is their cardinality, not their craft: one approved script becomes forty prompt variants, one storyboard becomes a shot table with forty rows, one review decision becomes a rule applied to a batch instead of an eyeball applied to a file. The moment a creative decision is fixed and counted it enters a queue, and the queue has an expiry, a ceiling and a bill. For the two stages where cardinality is decided — a script or voiceover whose length fixes what a shot must hold, and a shot list whose row count is your task count — this page assumes the number is already known. If it is not, start with best AI video generator to choose the model and come back with a model ID and a shot count.
Step 1: get the inputs in, and start the retention clock
The first decision is where the input file lives. The File API exists so you do not have to host it: POST https://api.modellix.ai/api/v1/media/files takes one file as multipart/form-data under the field name file, authenticated with the same Bearer key as every other call, and returns a URL you pass into a prediction input field such as image_url.
1 | curl --request POST \ |
The response carries file_id, type, url, filename, size and created_at. Uploads are not billed and not subject to balance admission, so staging assets costs nothing even on a thin balance; and they are time-boxed, retained about 7 days by default.
| Limit | Default | What it means for a pipeline |
|---|---|---|
| Max file size | 16 MB | fine for reference frames, tight for source video |
| Files per team | 10 | the binding constraint on a reference-heavy run |
| Concurrent uploads per team | 2 | two, not twenty — serialise your uploader |
| Retention | about 7 days | the clock starts at upload, not at submit |
The four numbers in the table above, as published, captured September 16, 2026. The admonition underneath is the operative one: files expire, and unused files should be deleted early if the per-team file count is close to the limit — the quota is counted in live files, not in uploads per day. Captured from docs.modellix.ai.
That ten-file ceiling is what changes architecture. A run of two hundred text-to-video tasks is unaffected. A run that puts a distinct reference frame behind every shot is not two hundred uploads — it is ten uploads plus a delete-and-reupload discipline, or a prompt-led design instead. Listing the media files shows what is still usable (page size defaults to 100, capped there); deleting a file frees quota immediately, and 404s if the file is already gone.
The retention window cuts both ways. A three-day render queue and a seven-day input window are compatible. A “regenerate next month from the same reference frames” feature is not — it needs its own asset store, with the upload as transient staging. If your image-to-video or text-to-video path treats the uploaded URL as a permanent copy of your source material, that breaks on day eight with a 404, after the job has been billed.
Step 2: submit a run, not a request
A submission returns immediately and hands you a handle, not a finished asset:
1 | { |
One GET later you have the result. Two of these fields are the ones most implementations ignore on the first pass:
| Field | What it is for |
|---|---|
status |
the task state; terminal states are success, failed and canceled |
task_id |
your join key to the logs and to the callback header |
model_id |
which model answered — the check that your routing did what you think |
duration |
wall-clock for the task, the number your run estimate needs |
billing.status / billing.amount |
what this task cost, at the task level |
result.resources[] |
url, type, width, height, format, role per output asset |
result.extensions.submit_time / end_time |
epoch-ms start and end |
result_expires_at |
when the result URL stops working |
The generated result URL is not a permanent address any more than the uploaded input is — a pipeline that stores a task_id and expects to fetch the mp4 next quarter is wrong at both ends. Retrieve, persist to your own storage, keep the task ID as provenance rather than as a retrieval strategy. model_id earns its own check: if you route by duration, resolution or price, this field tells you whether the routing logic picked the model you thought — how to pick a video generation model covers that decision.
For volume, one JSON object per line is enough:
1 | # tasks.jsonl — one JSON object per line |
model batch validates every line — slugs and bodies — before the first POST, requires --max-tasks or an explicit --yes because each line can create a paid task, caps its own concurrency between 1 and 10, and caps one invocation at 1000 tasks. The wait primitives are model run --wait --timeout for one task and task wait for many (up to 1000 IDs; a local timeout exits 124 while the remote task may still be running). task history records successful submissions locally, which is the difference between “the process that submitted this died” and “the task is lost”. The client is open source, so the batching behaviour is inspectable rather than taken on faith (modellix-cli on GitHub).
The batch contract, captured September 16, 2026: the JSONL line shape, the flags, and the safety guard that exists because every line is a paid task. Concurrency is limited to 1–10 and the absolute local limit is 1000 tasks per invocation. Captured from docs.modellix.ai.
One boundary: the 1–10 above is the CLI’s own ceiling, not what the API will accept. Your real ceiling is in Step 4.
The lifecycle the previous two sections describe: input in, task identity out, two alternative return paths, and a finished asset that has to be moved into your own storage. Diagram generated for this article; the two expiry windows on that path are the ones the surrounding text names.
Step 3: callbacks or polling — pick one and know what each promises
The decision
Polling every few seconds is fine for one job and wasteful for four hundred: every poll is a request against a query path with its own rate limit, separate from your generation throughput. A callback turns that per-task cost into one inbound POST.
The webhook contract
It is enabled per task, not per account: add an X-Webhook-URL header to the prediction creation request and the platform notifies you when that task reaches a terminal state. Callbacks fire on success, failed and canceled, and the callback body is identical to the task-result response, so one parser serves both paths. Six headers carry what your receiver needs:
| Header | Value |
|---|---|
Content-Type |
always application/json |
User-Agent |
always modellix-webhook/1.0 |
X-Modellix-Event |
prediction.task.succeeded / .failed / .canceled |
X-Modellix-Task-ID |
the task ID to join against |
X-Modellix-Delivery-ID |
unique per delivery attempt — your dedupe key |
X-Modellix-Retry-Count |
how many retries this delivery has had; starts at 0 |
The endpoint must be publicly reachable over HTTPS — not localhost, not a private IP range, no credentials in the URL. Acknowledge with a 2xx; the documented recommendation is 200 with a plain-text body of ok, or 204 empty, returning immediately after persisting the payload and doing the slow work off the request path.
The retry matrix
| Retried | Not retried |
|---|---|
429 Too Many Requests |
3xx redirection |
5xx server errors |
4xx client errors except 429 |
| network timeouts | invalid webhook URLs |
| temporary network errors | URLs pointing at private networks or localhost |
| permanent connection errors — connection refused, unresolved DNS |
Read the right-hand column as a list of quiet data losses. If your receiver answers 404 because a route was renamed, or cannot resolve because DNS was mid-change during a deploy, the platform will not try again — and the outcome is identical to a task that simply never finished: no callback. Nothing alerts you. Only a reconciliation sweep catches it: on a schedule, list tasks in the window and diff them against what your callback recorded.
Two details, one documented and one conspicuously not:
- Idempotency is documented.
X-Modellix-Delivery-IDis unique per delivery attempt;X-Modellix-Retry-Counttells you a redelivery is happening. Deduplicate on the delivery ID and a retried callback cannot render, bill or publish twice — the discipline HTTP semantics define for idempotent retries, applied to a delivery you did not initiate. - Signature verification is not documented. The docs advise verifying “the request source or signature (if signature verification is supported in a future update)” — a note about a possible future, not a scheme you can implement today. Treat the endpoint as an unauthenticated inbound route: an unguessable path segment or secret token in the registered URL, restricted at the network layer.
Polling is still right sometimes — a short interactive job, or a team that will not run an inbound endpoint. Callback for runs, polling for singles. Either way keep the retrieval parser shared, because that is the piece both paths agree on.
Video Task, Webhook and File API Reference
Read the endpoint reference for the X-Webhook-URL header, the callback headers and retry matrix, the task-result fields and the File API limits this article cites.
View Docs
The published retry rules, captured September 16, 2026. The orange block is the one worth reading twice: every line in it is a delivery that will never be retried, and none produces a signal you would notice. Captured from docs.modellix.ai.
Step 4: size the run against the limits your account actually has
The entitlement ladder
Concurrency and rate limits are properties of the account, keyed to the largest single top-up, not properties of the model:
| Single top-up | Concurrent tasks | Rate limit (RPM) |
|---|---|---|
< $10 |
2 | 100 |
$10 |
10 | 100 |
$100 |
20 | 200 |
$200 |
30 | 300 |
$500 |
50 | 500 |
$1,000 |
100 | 1,000 |
| Custom | Custom | Custom |
The published entitlement ladder, captured September 16, 2026, with the definition underneath it: concurrent tasks are asynchronous generation tasks running in parallel. The tier is set by a single top-up amount, so it is a spending decision before it is an engineering one. Captured from docs.modellix.ai.
Sizing the run
Take N tasks, C concurrent tasks, and T — the time a task takes end-to-end, measured on your own runs. The floor for the run is roughly N ÷ C × T. The task-level duration in the task receipt reports that same quantity and is documented in milliseconds (Processing time in milliseconds, example 3500), while the duration inside each result.resources[] entry is documented in seconds — that one is clip length, not wall-clock. Same field name, two units, one payload: read the task-level one for T, and do not read it as seconds, which would scale every estimate below by 1,000. T is also not clip length — a 40-second clip is a property of the delivered asset, while T is how long that task occupied a slot in your concurrency budget; measure the second and do not infer it from the first. Assume, purely for illustration, that a task takes 45 seconds end-to-end. On the $100 tier — 20 concurrent, 200 RPM — a 400-task run lands near 15 minutes, and RPM is not the binding constraint at that size. On the < $10 tier, with 2 concurrent tasks, the same 400 tasks take about two and a half hours. The number that sets your throughput is a number on your last top-up, not a number in your prompt.
Two ceilings bite before concurrency does: 2 concurrent uploads and 10 retained files per team. A run needing a fresh reference frame per shot spends its time in the upload queue, not the generation queue, and the fix is a design change, not a bigger top-up. When you do hit the wall, rate and concurrency exhaustion return the same 429 — the status code that means “you are being throttled” rather than “the request was wrong” — and the documented remedy is exponential backoff:
For
429responses, check theX-RateLimit-Resetheader to know when you can retry.
That header is the difference between a backoff that works and one that guesses. The full error contract is worth reading once, because the Retryable column decides which of your failures are your code’s fault and which are the platform’s:
The published error contract, captured September 16, 2026. 402 — insufficient balance — is explicitly not retryable and needs a recharge, which is why an unattended run should watch the balance rather than retry through it; 500 says retry up to three times; 429 and 503 both say use backoff. Captured from docs.modellix.ai.
One caveat: limits change without notice, the custom tier is a conversation rather than a number, and the only place showing your entitlements is your own console.
Step 5: reconstruct the bill per task, not per month
A batch run you cannot account for is one you cannot safely increase. GET https://api.modellix.ai/api/v1/logs returns paginated rows for the team that owns the key and requires a window of at most 30 days.
1 | { |
Log fields and the 30-day window read from the Get Logs reference on September 16, 2026.
task_id is the same join key the callback carries, which makes reconciliation a single pass. Diff three sets — submitted, callback-recorded, billed — and three failure classes separate cleanly: finished-and-billed but never delivered (a non-retried callback), submitted twice (a missing idempotency check), and failed but still carrying a billing row. None of those is visible from a monthly total.
The log path has its own rate limit, separate from media generation RPM. For a multi-tenant product, tag each submission with an end-user identifier in the X-Mdlx-User-Id header — 8 to 128 characters, ASCII letters, digits, - and _ — so cost is attributable per customer later; the log list does not echo that field back, so you filter by the matching query parameter. If you already log model calls, this is the same split as agent observability: one log line per unit of work, joined by an identifier you control.
One number worth knowing before you ship
Whether a generated asset carries a visible watermark is not documented per model on any page read for this article. If your product publishes output publicly, confirm watermarking behaviour per model before launch rather than assuming it, and check the terms that apply to your use — a product decision with a commercial dimension, and nothing here should be read as legal advice. The same caution covers what you upload: rights to a reference frame or a person’s likeness are yours to hold, and the File API does not check that for you.
Reconstruction is not prediction. The estimating half belongs to the cost calculator and the per-second table in the video model price comparison — linked rather than reproduced, because a price table copied into an article is wrong within a week.
The whole run, end to end
Every endpoint, header and field here is one documented above or in the reference:
1 | # 1. stage the input (not billed; expires in ~7 days) |
Three things there are load-bearing and easy to drop in a refactor: the webhook header is set on the submission, not on your account, so it does not survive a code path that forgets it; step 3 must be idempotent, because the platform can redeliver; and step 4 is not optional if you intend to keep the file.
Check Your Own Concurrency Tier and Balance
Log in to see the entitlements and balance on your team before you size a batch run, and to read per-task cost in your own request log.
LoginThe failure modes a batch run will actually hit
Following all of the above does not make a run safe. It makes the failures visible, which is more useful:
- The silent callback loss. A non-retried
4xxor a DNS failure during a deploy lands in the same observable state as a task that never finished. - The expired input. A run spanning more than about a week cannot rely on uploads made at its start.
- The expired result. A pipeline that retrieves lazily eventually retrieves nothing.
- The
402mid-run. Insufficient balance arrives part-way through a run, leaving it half produced and half billed. Watch the balance on unattended schedules and set the console’s low-balance alert. - The ceiling you find at runtime. Concurrency exhaustion returns
429, the same code as a rate limit, so it looks like provider trouble when it is usually your own tier.
Two constraints here are limitations rather than features: ten retained files per team is small for reference-heavy work, and the absence of a documented webhook signature scheme means your receiver must be secured by other means. Nothing above argues that this contract is the best one available — the operational numbers come from one API’s documentation as read on September 16, 2026. What the single-key model is worth here is narrower and checkable: the same credential and the same submit-and-callback shape cover the image, video and audio models across the providers behind it, so a pipeline written once against these fields does not need a second auth path when a model changes.
Frequently Asked Questions
How do I automate AI video generation instead of doing it by hand?
Make the task submit itself and stop watching it. Upload inputs to a File API, submit each task with an X-Webhook-URL header so the platform calls you at a terminal state, deduplicate on X-Modellix-Delivery-ID, and reconcile submitted against billed tasks in GET /api/v1/logs. The generation step already works; the automation is the plumbing around it.
How do I generate an AI video step by step with an API?
Stage, submit, get notified, retrieve, reconcile. POST /api/v1/media/files returns a url for your input; POST /api/v1/<provider>/<model> with X-Webhook-URL returns a task_id; your receiver takes the callback and dedupes it; GET /api/v1/tasks/<task_id> returns result.resources[], downloaded before result_expires_at; GET /api/v1/logs gives the billed cost per task.
How long does a video generation task take, and how long will a batch take?
Read per-task wall clock from duration (the task-level one is in milliseconds; the asset-level duration inside result.resources[] is in seconds) rather than assuming, then estimate N ÷ C × T for N tasks at C concurrent tasks and T seconds each. Concurrency comes from your entitlement tier, so the same run can be fifteen minutes on one tier and several hours on the entry tier. Neither number is published per model.
Should I use a webhook or poll for task status?
Callback for runs, polling for singles. A webhook costs one inbound request per task and takes pressure off a query path with its own rate limit; polling costs one request per interval per task and needs no inbound endpoint. The callback body and the task-result response are identical, so keep one parser.
What happens if my webhook endpoint is down when the task finishes?
It depends on the error, and the distinction is the operational risk. 429, 5xx, network timeouts and temporary network errors are retried. 3xx, 4xx other than 429, invalid or private-network URLs and permanent connection errors are not, and a task hitting that list produces no callback at all — identical to a task that never completed. A reconciliation sweep is not optional.
How long do uploaded input files and generated results stay available?
Uploads are retained about seven days and are not billed; generated results have their own window, published on each task response as result_expires_at. Neither is permanent, so move anything you need to keep into your own storage as soon as the task completes.
How many video generation tasks can I run at once?
It is set by your entitlement tier, keyed to the largest single top-up — from 2 concurrent tasks and 100 requests per minute at entry up to 100 concurrent and 1,000 RPM at the top published tier. Rate-limit and concurrency exhaustion both return 429, remedied by backoff with the reset time in X-RateLimit-Reset. Your current entitlements are in your own console.
How do I find out what a batch run cost?
Read GET /api/v1/logs over a window of at most 30 days; each row carries task_id, status, the serving model.provider and model.model_name, created_at and cost. Join those rows to the task_id values you submitted and to the ones your callbacks recorded to separate completed-and-billed, billed-but-lost and submitted-twice.
Platform behaviour, limits and endpoints described here reflect first-party documentation read on September 16, 2026, and change without notice; verify against the live pages before building on any figure. The numbers and contracts come from the REST API guide, the File API, query task result, the request log reference, the CLI documentation, the team entitlements page and the product changelog. Modellix is an API aggregator and the contract described is its own, which is why the figures are first-party and why two of the constraints named are limitations rather than features; it is not the only place these models can be called. Reach image, video and audio models through one key at modellix.ai.