Modellix cover: a glass API gateway issuing a task_id that fans out to three parameter cards for duration, resolution and aspect ratio.

The first bug in most video generation API integrations is not a bad prompt. It is a POST that returns in 200 milliseconds with no video in it — because media generation is asynchronous, and the first response is a receipt, not the result. Developers arriving from a chat completion API expect the payload to be the answer; here it is a task_id, a status of pending, and a URL to come back to later.

This article covers the shape every video generation API shares — submit, poll, retrieve — and then the part where the models stop agreeing: the request body. On September 16, 2026 we pulled the published request schema for 22 video models across eight providers; that comparison, not the pricing, is where the integration cost lives. Two disclosures: we run Modellix, an API aggregator, so we have a commercial interest in the aggregator half of what follows; and this piece stops at “the first request works”, leaving batch runs, webhooks and log reconciliation to a separate topic linked at the end.

What every video generation API has in common

A video generation API is an HTTP interface that takes a prompt, and usually an image or a video, and returns a generated video file — never inside the response that submitted the job. The submit call returns a task identifier; a second call retrieves the output once rendering finishes. That two-call pattern, not any particular URL, is the interface you integrate against, on text-to-video, image-to-video and video-to-video routes alike.

Authenticate with a bearer API key; POST a JSON body to https://api.modellix.ai/api/v1/{provider}/{model}; poll GET /api/v1/tasks/{task_id} until the status is terminal; read the media URLs and the task cost out of the result.

One boundary trips people up first. Modellix runs three hosts that do not behave the same: api.modellix.ai is asynchronous media generation, llm.modellix.ai is synchronous text, tool.modellix.ai handles web search and fetch. The platform overview states the rule plainly: use “the host that matches the product”, and do not send “media jobs to the LLM gateway or chat requests to the media API”. The reason is this article’s subject: the media host answers with a task, the LLM host with a completion, and a client built for one misreads the other’s success.

Your first call: one endpoint, a task ID, and a status you come back for

Model differences are real; the envelope is not one of them. Every route is invoked the same way:

1
2
3
4
5
curl --request POST \
--url https://api.modellix.ai/api/v1/google/veo-3.1-t2v \
--header 'Authorization: Bearer <your_api_key>' \
--header 'Content-Type: application/json' \
--data '{"prompt": "A slow dolly shot through a rain-lit Tokyo alley at night"}'

A successful submit answers with the receipt:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"code": 0,
"message": "success",
"data": {
"status": "pending",
"task_id": "task-abc123",
"model_id": "veo-3.1-t2v",
"get_result": {
"method": "GET",
"url": "https://api.modellix.ai/api/v1/tasks/task-abc123"
}
}
}

Read it literally. code: 0 means the submission was accepted, not that a video exists. data.status describes the task, and the task result reference enumerates four values: pending, processing, success and failed. get_result.url is where to look next, returned so you never construct the URL yourself. The polling side is short:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os, time, requests

API = "https://api.modellix.ai/api/v1"
HEAD = {"Authorization": f"Bearer {os.environ['MODELLIX_API_KEY']}"}

submitted = requests.post(
f"{API}/google/veo-3.1-t2v", headers={**HEAD, "Content-Type": "application/json"},
json={"prompt": "A slow dolly shot through a rain-lit Tokyo alley at night"},
timeout=30,
).json()["data"]

while True: # {"success", "failed"} are the terminal states
task = requests.get(f"{API}/tasks/{submitted['task_id']}", headers=HEAD, timeout=30).json()["data"]
if task["status"] in {"success", "failed"}:
break
time.sleep(5)

if task["status"] == "failed":
raise RuntimeError(task.get("error", "generation failed"))
for resource in task["result"]["resources"]:
print(resource["type"], resource["url"])

Two details matter more than they look. The terminal set includes failed — a client that only exits on success polls a dead task until its own timeout kills it, and the reason sits in task["error"]. Five seconds is a starting interval, not a contract: cap the attempt count.

If you inherited code posting to a path ending in /async, leave it alone. The suffix was removed from model invocation paths on June 11, 2026, and the product changelog states the old paths keep working with full backwards compatibility — the retired form still appears in the documentation’s own examples, below.

Modellix REST API documentation showing the Use the API step with a curl POST to the model endpoint, a bearer Authorization header, and a JSON prompt body, captured September 16, 2026

The submit step as the vendor documents it, captured September 16, 2026 from docs.modellix.ai/ways-to-use/api. The response block sits just below the fold, which is itself the point: the call you write first returns a task, not a file. The model path in this example still carries the retired /async suffix.

What a finished task returns — and when it stops being there

The retrieval call answers with a different shape, and one field in it has a shelf life:

Field What it tells you
data.status success or failed — the terminal states
data.duration Wall-clock processing time in milliseconds, returned on completion or failure
data.result.resources[] Outputs, each with url, type, plus width, height, format and role
data.billing status (pending or succeeded) and the actual charge as amount in USD
data.result_expires_at Unix millisecond timestamp for when the generated file is deleted

That last row is the one people skip. Generated media is retained for 7 days — the documentation is direct: “Generated results are only saved for 7 days, so please make sure to save them promptly.” Store the bytes, not the URL: an integration that writes resource.url into a product database renders a broken player next week, and it looks like a CDN problem rather than a day-one design choice.

One optional piece belongs on the submit call rather than the response. X-Mdlx-User-Id tags a task with your own end-user identifier so request logs can be filtered by user later; it must be 8 to 128 characters of ASCII letters, digits, hyphens and underscores, and an invalid value returns 400. If your product serves several customers from one key, that header keeps their usage separable.

data.billing.amount is worth wiring up early: it is the billed figure for that call, and on resolution-priced models the only number that reconciles against an invoice. The pricing documentation is explicit that a video model can cost more at 1080p than at 720p, so a headline rate drifts and the per-task amount does not. Our cost calculator for video generation works that arithmetic through per model; the live price table is where the per-second rates live.

Where the models stop agreeing: the request body

The envelope is uniform; the body is not. That is what produces a 400 Invalid parameters on a request where you clearly did send the parameter — the field carrying duration, resolution or aspect ratio is named differently per provider.

We queried the public schema endpoint — GET https://www.modellix.ai/models/{provider}/{slug}/api_schema, no account required — for 22 video models on September 16, 2026. Six providers are enough to show every naming convention in the set. One convention for reading it: each column is one provider, and where a value comes from that family’s image-to-video route rather than its text-to-video route, the cell says so.

Google (Veo 3.1) ByteDance (Seedance 2.5) Alibaba (Wan 3.0) Kling (v3) PixVerse (v6) xAI (Grok Imagine)
Casing aspectRatio, negativePrompt ratio ratio aspect_ratio aspect_ratio aspect_ratio
Duration string enum "4", "6", "8" integer enum 4–30 integer enum 2–30 integer enum 3–15 (t2v) integer 1–15 integer 1–15
Resolution field resolution: 720p, 1080p, 4k resolution: 480p, 720p, 1080p resolution: 480P, 720P, 1080P resolution: 720p, 1080p, 4k quality: 360p1080p resolution: 480p, 720p
Audio field none on the T2V route generate_audio audio audio generate_audio_switch none
Source frame (i2v routes) image + lastFrame first_frame_image + last_frame_image first_frame + last_frame image + image_tail image image
Extra knobs personGeneration camera_fixed, return_last_frame seed, file_url, link_url multi_shot generate_multi_clip_switch, seed

One note on the PixVerse column: the i2v route takes the source frame as image. reference_images belongs to pixverse/v6-r2v, a separate reference-to-video route, and is not a field this route accepts.

Three things fall out of it, and each has cost somebody an afternoon.

Casing is not cosmetic and it is not consistent. Google’s video models take camelCase (aspectRatio, negativePrompt, personGeneration), as do its image routes; ByteDance, Alibaba, Kling, PixVerse, Vidu and xAI all take snake_case. A body copied between providers needs the key names changed, not just the values. (Google’s own Gemini video documentation splits its video work between two models with different workflows.)

The same logical value arrives in three shapes. duration is a string enum on Veo 3.1 ("4", "6", "8"), a closed integer enum on Seedance 2.5 (4 to 30) and Wan 3.0 (2 to 30), and a plain 1-to-15 integer range on PixVerse and xAI. Send 8 to Veo and it wants "8".

Resolution differs in case and in name. Wan 3.0 accepts 480P, 720P and 1080P with a capital P; MiniMax is inconsistent inside one provider, publishing 768P and 1080P on Hailuo 2.3 but 768P and 2K on H3; Seedance, Kling, Vidu and xAI take lowercase. PixVerse has no resolution field at all — it takes quality, with a 360p-to-1080p enum. A helper that lowercases resolution breaks on two providers, one that assumes the field is called resolution breaks on a third, and one that caps at 1080p is wrong on the routes that reach 2K and 4k. The comparison above is drawn from our own published schemas, which is a disclosure worth making twice: we sell aggregation, and we are not claiming the field names are worse elsewhere — only that they are different, and that the difference is the integration work.

Request schema for an image-to-video model: a oneOf split into Image and Reference modes, with aspectRatio, duration, image and resolution.

The public schema for one image-to-video route, captured September 16, 2026 from modellix.ai/models/google/veo-3.1-fast-i2v/api_schema. The JSON is re-formatted in the browser so the field names are legible at this size; the payload itself is the endpoint’s own response.

Required fields differ more than the names do

Names are the visible half. The required set decides whether your first request returns 200 or 400, and it varies more:

Model Required beyond prompt
google/veo-3.1-t2v none — prompt alone is enough
kling/kling-v3-t2v none
vidu/viduq3-pro-t2v none
xai/grok-imagine-video none
pixverse/v6-t2v aspect_ratio, duration, quality — four fields total
minimax/minimax-h3-v2v duration, resolution, reference_videos — four fields total

A client that posts {"prompt": "..."} and treats the rest as optional succeeds on roughly half the catalog and fails on the other half, one missing field at a time.

Under the required set is a second layer: mutually exclusive modes. Several image-to-video routes are published as a oneOf with two branches, and the branch changes which fields exist and what values they accept. Veo 3.1’s fast route is the clearest case: Option 1 takes image plus an optional lastFrame, Option 2 takes referenceImages, and the two cannot be combined:

Branch Required Notably constrained
Option 1 — Image mode prompt, image duration allows "4", "6", "8"; using lastFrame forces "8"
Option 2 — Reference mode prompt, referenceImages duration allows "8" only

Wan 3.0 splits the same way with one extra trap: audio_urls is valid only in reference mode, so a body carrying audio alongside a first frame is rejected even though both fields appear on the same page.

Sound is the other place families diverge, and it is a product decision rather than a naming one. Some routes generate video with a synchronised soundtrack — Seedance calls it generate_audio, Kling and Wan call it audio, PixVerse calls it generate_audio_switch — while Google’s text-to-video route has no audio field at all. If your product promises a clip with sound, that narrows the shortlist before price or resolution gets a vote. MiniMax documents the same three-way structure — text-to-video, image-to-video, reference generation — so read its H3 generation guide next to the schema rather than instead of it. Our image-to-video API guide walks the first-frame and reference workflows model by model.

How to read any model’s schema before you write the body

You do not have to guess or sign up. Two public endpoints and one CLI command cover it:

1
2
curl -s "https://www.modellix.ai/models/bytedance/seedance-2.5-t2v/api_schema" \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['servers'][0]['url']); print(sorted(d['post']['requestBody']['content']['application/json']['schema']['properties']))"

Run against six models, that line produces the table above — which is the point. We publish the endpoint and have a commercial interest in you finding it useful; it needs no account, so nothing here asks you to trust us rather than check. The schema reference notes the slug pattern is provider/model, the slash a literal path separator rather than %2F. Its sibling, the model list endpoint at GET https://api.modellix.ai/api/v1/models, returns every active model with its slug, type, docs_url, description and a display price range — how “which models take a first frame” becomes a list instead of a memory.

REST, SDK, or CLI — what a “video generation SDK” question is really asking

Three different products get sold under one set of names. Ask for an “AI video generator API” and you may be handed a typed client library, a command-line tool, or an agent plugin — worth knowing which you want, because the AI video API underneath is the same in all three cases: plain JSON over HTTPS with one envelope. A REST API plus generated types makes a 20-line client a legitimate answer. A CLI suits batch submission and schema inspection, and is the only one that keeps a task history locally. An agent skill or plugin packages schema lookup for coding agents — useful when an agent writes the integration, irrelevant when a human does (CLI documentation covers model get-schema, model list and task wait).

The SDK layer is thin because the API is small: a heavier client library does not remove the field-name problem, it relocates it.

One boundary worth stating, because the query is ambiguous: searching for how to generate video programmatically also surfaces React-based rendering tools that compose video from code rather than generating it from a model. Both are legitimate, but if your goal is compositing, the text-to-video API landscape is not where you should be reading.

The errors worth handling before you ship

Every error returns the same JSON shape — {"code": 400, "message": "..."}, where code mirrors the HTTP status and 0 means success — so one handler covers the set if you branch on status.

Status Meaning Retry?
400 Missing or invalid parameter No — fix the body
401 Invalid, missing or expired API key No
402 Insufficient balance No — the account needs funding
404 Task, model or provider not found No — check the identifier
429 Rate or concurrency limit exceeded Yes — read X-RateLimit-Reset, then back off
500 / 503 Internal error, or service unavailable Yes — exponential backoff, 1s → 2s → 4s

402 deserves its own branch because it will not fix itself: the balance is empty and retries only add noise. 429 deserves care because two limits share it — concurrency caps how many async tasks run at once, RPM caps request rate, and both scale with the largest single top-up that funded the account, from 2 concurrent tasks and 100 RPM below $10 up to 100 and 1,000 at a $1,000 top-up, per the published entitlements table. Beyond the top tier it is a vendor conversation, not a config flag.

What this article deliberately leaves out

Everything past the first working request belongs to the production layer. Webhook delivery: sending X-Webhook-URL on submit makes the platform POST the result at a terminal state, so the polling loop can go away — retry rules and idempotency keys are a topic in themselves. Batch submission: hundreds of tasks means queueing and concurrency budgeting against the limits above. File inputs at scale: the File API accepts image, video and audio uploads free of charge, but files expire after about 7 days under a 16 MB per-file cap, 10 files per team and two concurrent uploads. For the pipeline question, our model API pricing comparison and the roundup of AI video generation APIs are the right next reads.

Frequently Asked Questions about the video generation API

Which video generation API is best?
No list answers that without your constraints. The productive version is which models accept the parameters you need — a 30-second clip, an audio track, a reference image, a specific aspect ratio — so pull the schemas, filter on those fields, then compare per-second cost. One live caveat: OpenAI’s video generation guide opens with a notice that the Sora 2 models and the Videos API are deprecated and shut down on September 24, 2026 (deprecations page) — worth knowing before a May-dated roundup lists one as an option. Our model-by-model comparison treats selection in that order.

Is there a free video generation API?
Modellix removed the signup credit on August 19, 2026, so new accounts no longer start with a balance; media generation is billed per call from a pay-as-you-go balance. A free tier is a property of a specific promotion, not of the category — anyone advertising free video generation API access is describing their own trial terms, not a general feature of the market.

Why does my request return a task instead of a video?
Because generation is asynchronous and the submit response is an acknowledgement. Read data.task_id and data.get_result.url, then poll until data.status is success or failed. You are not wrong — you are one call early.

Do I need an API key to inspect a model’s request schema?
No. The schema endpoint is public and the CLI’s model get-schema calls it without credentials. You need a key to submit a generation, not to learn what a valid body looks like.

How long do generated videos stay available?
Seven days, with result_expires_at in the response as a Unix millisecond timestamp. Download and store the media; do not persist the URL as your only copy.

Can I generate video from Python?
Yes — the API is plain JSON over HTTPS, so requests is enough and no vendor SDK is required. Any client that can set an Authorization header, POST a body and read a nested status field will work; the loop above is 15 lines.

Media Model Schema Reference

Read any video model's request schema and the async task response shape before you write the next request body.

View Docs

Run the Schema Check on a Real Model

Log in to submit one video task on your own key and read the per-task amount and expiry from the response.

Login

Provider details, endpoint behaviour and pricing reflect public information as of September 2026 and change frequently. Validate against the live documentation and each provider’s current pricing before committing. Access image and video models, including the leading Chinese models, through a single API key at modellix.ai.