Video generation
Video runs as an async task — submit → poll status → download the result. Supports Seedance, Wan, Grok Imagine, MiniMax H3, HappyHorse, Gemini Omni and more.
Workflow
Video generation takes anywhere from tens of seconds to over ten minutes, so it runs as an async task:
- Submit the task: a task ID is returned immediately and the task is queued.
- Poll the status: query every 5–10 seconds until it succeeds or fails.
- Download the video: on success, download it from the returned link. Links expire (usually after 24 hours) — copy the file to your own storage promptly.
Depending on the model, there are two request formats:
| Model family | Format | Submit endpoint |
|---|---|---|
Seedance (doubao-seedance-*, dreamina-seedance-*) |
Volcengine Ark-compatible | POST /api/v3/contents/generations/tasks |
| Wan, Grok Imagine, MiniMax H3, HappyHorse, Gemini Omni, etc. | OpenAI video format | POST /v1/video/generations |
For available video models and prices, see the model catalog (search for keywords such as seedance, wan, video).
Seedance (Volcengine Ark-compatible format)
Request and response formats match the official Volcengine Ark Seedance API; existing Ark code only needs a new URL and key.
doubao-seedance-*: China edition;dreamina-seedance-*: international edition. Both take the same parameters.-fast(faster) and-mini(cheaper) variants are also available.
Submit
cURL
curl https://api.onefast.ai/api/v3/contents/generations/tasks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "dreamina-seedance-2-0-260128",
"content": [
{"type": "text", "text": "A cat stretching on a sunny windowsill, slow camera push-in"}
],
"ratio": "16:9",
"duration": 5,
"resolution": "720p",
"generate_audio": false
}'
Response:
{ "id": "mvt-3d602a25b8da4b00", "model": "dreamina-seedance-2-0-260128", "status": "queued", "created_at": 1790824063 }
Query
cURL
curl https://api.onefast.ai/api/v3/contents/generations/tasks/mvt-3d602a25b8da4b00 \
-H "Authorization: Bearer $API_KEY"
On success:
{
"id": "mvt-3d602a25b8da4b00",
"model": "dreamina-seedance-2-0-260128",
"status": "succeeded",
"content": { "video_url": "https://.../video.mp4?..." },
"duration": 4,
"resolution": "480p",
"ratio": "16:9",
"usage": { "completion_tokens": 40594, "total_tokens": 40594 }
}
status values: queued → running (generating) → succeeded / failed (see the error field).
Common parameters
| Parameter | Description |
|---|---|
model |
Required |
content |
Required. An array: {"type":"text","text":"..."} is the prompt; {"type":"image_url","image_url":{"url":"..."},"role":"first_frame"} is a first-frame image (role can be first_frame / last_frame / reference_image); {"type":"video_url","video_url":{"url":"..."},"role":"reference_video"} is a reference video |
duration |
Duration in seconds. 2.0 series: 4–15; 2.5 series: 4–30 |
resolution |
480p / 720p (default 720p) |
ratio |
16:9, 9:16, 1:1, 4:3, 3:4, 21:9 or adaptive (default; adapts to the input media) |
generate_audio |
Generate audio, default true |
watermark |
Add a watermark (optional) |
seed |
Random seed |
- First/last frames and reference images (
reference_image) can't be used together; up to 30 reference images. - Reference videos (
reference_video): the 2.0 series accepts up to 3 clips totalling at most 15 seconds; the 2.5 series up to 10 clips totalling at most 30 seconds. Video URLs must be publicly accessible. - Image URLs must be publicly accessible
https://URLs of at most 4000 characters, orasset://<Id>references from the asset library. Do not put base64 ordata:URLs inimage_url: such requests are rejected with 400InvalidParameter(… is too long (… max 4000)) and are not billed. For local files, upload them to the private asset library first (see below).
Image-to-video example
{
"model": "dreamina-seedance-2-0-260128",
"content": [
{"type": "text", "text": "The girl turns around and smiles"},
{"type": "image_url", "image_url": {"url": "https://example.com/first.jpg"}, "role": "first_frame"}
],
"duration": 5,
"resolution": "720p"
}
Draft mode (Seedance 2.5)
Render a cheap 480p draft first to check framing, camera movement and how the prompt was understood, then turn the draft you like into a 1080p final that closely matches it. You only pay the 1080p price for the shot you keep.
1. Render a draft. Submit as usual with "draft": true; resolution must be 480p.
cURL
curl https://api.onefast.ai/api/v3/contents/generations/tasks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "dreamina-seedance-2-5-260628",
"content": [{"type": "text", "text": "An orange cat chasing butterflies in a sunny garden, smooth tracking shot"}],
"draft": true,
"resolution": "480p",
"duration": 5,
"ratio": "16:9"
}'
2. Render the final from the draft. Once the draft has succeeded, put a single draft_task item in content and set resolution to 1080p:
cURL
curl https://api.onefast.ai/api/v3/contents/generations/tasks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "dreamina-seedance-2-5-260628",
"content": [{"type": "draft_task", "draft_task": {"id": "mvt-3d602a25b8da4b00"}}],
"resolution": "1080p"
}'
- Supported models:
dreamina-seedance-2-5-260628,doubao-seedance-2-5-260628. Other models return 400. draft_task.idis the draft's task ID (mvt-…); the draft'smetadata.id(cgt-…) is accepted too. The draft must have been submitted with the same API key, must have succeeded, and is valid for 7 days.- In the final request,
contentmust contain only thedraft_taskitem andmodelmust match the draft. The prompt, reference media,duration,ratio,seedandgenerate_audioare inherited from the draft; sending them returns 400.watermark,return_last_frameandexecution_expires_aftercan be set again. - Billing: the draft is billed at 480p; the final is billed at 1080p for the same duration as the draft. If the draft used a reference video, the final is billed at the reference video input tier as well.
- When queried, a draft returns
"draft": true; a final returnsdraft_task_idpointing to its draft.POST /v1/video/generateaccepts the samedraft/draft_taskfields.
Real-person content moderation
This check applies to Seedance models only. Other video models, such as Wan and MiniMax-H3, are not subject to it.
Every image, video and audio input of a Seedance task is checked by the upstream provider before generation. Inputs that show real, identifiable people are rejected:
| Error code | Meaning |
|---|---|
InputImageSensitiveContentDetected.PrivacyInformation |
A reference image or first/last frame may contain a real person |
InputVideoSensitiveContentDetected.PrivacyInformation |
A reference video may contain a real person |
- Each input is checked on its own. If both the image and the video show real people, replacing only one of them is not enough.
- The check is applied to the media itself. Describing a real person as "fictional" in the prompt does not change the result.
- The check is not limited to visible faces. Realistic bodies, hands, silhouettes or figures can also trigger it, and so can photorealistic AI-generated people.
- Rejected tasks are not billed; the pre-charge is refunded in full.
- For choreography or motion references, use footage that does not show identifiable real people.
To use a consistent fictional character, add it to the private asset library below.
Private asset library (virtual characters)
Seedance's input moderation may reject images that contain realistic human faces (errors such as InputImageSensitiveContentDetected). If you need a fictional or AI-generated character to appear consistently across videos, upload it to the private asset library first, then reference it in content by its asset ID. Library assets are trusted inputs and are not blocked by that check.
Real people are not allowed. Assets must not resemble any real person — do not upload photos of real people, including yourself, your customers, employees or public figures. Only fictional or AI-generated characters are permitted. Uploading real-person likenesses violates the upstream provider's terms and may lead to your account being suspended.
This restriction applies to Seedance only; Wan and MiniMax-H3 are not subject to it.
1. Create the asset. Set model to the model you will generate with. URL must be publicly accessible; AssetType is Image, Video or Audio.
cURL
curl https://api.onefast.ai/v1/sd/assets \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "dreamina-seedance-2-5-260628", "URL": "https://example.com/fictional-character.png", "Name": "fictional-character-01", "AssetType": "Image"}'
{ "success": true, "data": { "Id": "mva-e144dfc364a647f1", "Ref": "asset://mva-e144dfc364a647f1", "Status": "Processing", "AssetType": "Image", "Name": "fictional-character-01", "Model": "dreamina-seedance-2-5-260628", "Error": null } }
Assets are valid for 7 days; re-upload after that. Use data.Ref directly in content.
2. Wait until it is active. Query GET /v1/sd/assets/{Id} until data.Status is Active (usually a few seconds).
cURL
curl https://api.onefast.ai/v1/sd/assets/mva-e144dfc364a647f1 \
-H "Authorization: Bearer $API_KEY"
3. Reference it in a task with asset://<Id> wherever an image, video or audio URL is accepted:
{
"model": "dreamina-seedance-2-5-260628",
"content": [
{"type": "text", "text": "The character walks through a rainy neon street at night"},
{"type": "image_url", "image_url": {"url": "asset://mva-e144dfc364a647f1"}, "role": "reference_image"}
],
"duration": 5,
"resolution": "720p"
}
The same asset:// references also work with POST /v1/video/generate.
Rules:
- Same model: the task's
modelmust be exactly themodelgiven when the asset was created. An asset created fordreamina-seedance-2-5-260628cannot be used withdreamina-seedance-2-0-260128. - Same model for all assets in one request: every asset referenced in a single task must have been created for the same model.
- Same API key: an asset can only be queried and referenced with the API key that created it. Asset IDs from your own BytePlus / Volcengine account cannot be used.
- Supported models: currently
dreamina-seedance-2-0-260128,dreamina-seedance-2-0-fast-260128,dreamina-seedance-2-0-mini-260615,dreamina-seedance-2-5-260628,doubao-seedance-2-0-260128,doubao-seedance-2-0-fast-260128,doubao-seedance-2-0-mini-260615,doubao-seedance-2-5-260628. - Creating and querying assets is free; video tasks that use assets are billed as usual.
| Error | Cause |
|---|---|
403 video asset … was not found for this API key |
The asset was not created via POST /v1/sd/assets, or was created with a different API key |
403 The requested model does not match the model the referenced video asset was created with |
model differs from the model used when creating the asset |
400 all asset references must belong to the same video channel and model |
Assets created for different models are mixed in one request |
409 Reference material '…' is still processing |
The asset is not Active yet; wait a few seconds and retry |
400 video asset … was created with a retired upload flow |
The asset was created before 2026-10-15; re-upload it and use the new reference |
Failure error codes
When a Seedance task fails — at submission or later in the task's error field — error.code tells you what to do next. Failed tasks are not billed; any pre-charge is refunded in full.
error.code |
Meaning | What to do |
|---|---|---|
invalid_parameter / InvalidParameter.* |
A request parameter was rejected | Fix the parameter named in message, then resubmit |
content_moderation_rejected / *SensitiveContentDetected.* |
The input or output was rejected by content moderation | Replace or revise the content; resubmitting the same content will be rejected again |
video_download_failed |
A reference image / video / audio URL could not be downloaded | Make sure the URL is publicly accessible (no login, expired signature, hotlink protection or IP restriction) |
video_processing_timeout / video_processing_error |
Temporary error in the generation service | The content does not need to change; retry |
video_submit_timeout |
The submission timed out and could not be confirmed | The content does not need to change; retry |
concurrent_task_limit |
The concurrent task limit for this model has been reached (HTTP 429 at submission) |
Wait for running tasks to finish, then submit again |
service_temporarily_unavailable |
The service is temporarily unavailable (HTTP 503 at submission) |
Retry later |
video_generation_failed |
Other failure | Retry; if it keeps failing, contact support with the task ID |
Billing
Billed on the usage.completion_tokens returned by the upstream (determined by resolution, duration and aspect ratio; 480p / 4 seconds is about 40,000 tokens). An estimate is pre-charged on submission and settled up or refunded on completion; failed tasks are refunded in full automatically.
Token prices come in two tiers, both listed in the model catalog:
| Tier | Applies when | Notes |
|---|---|---|
| Standard | Text only, first/last frames, reference images, reference audio | The main unit price in the catalog |
| Reference video input | content contains a reference video (reference_video) |
Lower unit price (about 60% of Standard), but token usage counts output duration + reference video duration |
Reference videos must be passed as video_url items in the content array with role set to reference_video, and the URL must not be empty. The reference video input tier applies only when a reference video is actually submitted upstream with the task; otherwise the Standard tier applies.
For tasks billed at the reference video input tier, an extra amount is pre-charged at submission based on the maximum reference length (15 seconds for the 2.0 series, 30 seconds for the 2.5 series) and settled against actual usage on completion. With a low balance, the submission may be rejected for insufficient pre-charge.
Wan / Grok / MiniMax H3 / HappyHorse / Gemini Omni (OpenAI video format)
Submit
cURL
curl https://api.onefast.ai/v1/video/generations \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-imagine-video",
"prompt": "A paper boat drifting on a rainy street, cinematic",
"duration": 5
}'
Response:
{ "id": "task_piMMxpEa...", "task_id": "task_piMMxpEa...", "object": "video", "model": "grok-imagine-video", "status": "queued", "progress": 0 }
Query
cURL
curl https://api.onefast.ai/v1/video/generations/task_piMMxpEa... \
-H "Authorization: Bearer $API_KEY"
On success, status is completed and the video URL is in the url field (some models also return video_url):
{ "id": "task_piMMxpEa...", "object": "video", "status": "completed", "progress": 100, "url": "https://.../video.mp4" }
status values: queued → in_progress → completed / failed.
Parameters by model
| Model | Duration (s) | Resolution | Other |
|---|---|---|---|
grok-imagine-video |
1–15 | Default | Supports aspect_ratio, seed, negative_prompt |
happyhorse-1.1-t2v |
3–15 | Default 1080P | Same as above |
wan-2-7-video |
2–15 | 720p | aspect_ratio: 16:9 |
wan-3-0-video |
2–30 | 480p / 720p / 1080p |
Same as wan3.0-video; see "Wan 3.0 family" below |
gemini-omni |
Fixed at 4 | 720p | aspect_ratio: 16:9 |
Common fields: model (required), prompt (required), duration (or seconds), resolution, aspect_ratio.
Grok Imagine 1.5 and the MiniMax H3 family
grok-imagine-video-1.5, minimax-h3, minimax-h3-2k, minimax-h3-max and minimax-h3-max-turbo are also submitted with POST /v1/video/generations and polled with the query endpoint above. Parameters:
- Duration goes in
duration(whole seconds, required);secondsalso works. - Aspect ratio goes in
ratio;aspect_ratioalso works. If both are sent,duration/ratiowin. - Pass a first-frame image URL in
image.
cURL
curl https://api.onefast.ai/v1/video/generations \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-imagine-video-1.5",
"prompt": "A paper boat drifting on a rainy street, cinematic",
"duration": 5,
"resolution": "720p",
"ratio": "9:16"
}'
| Model | Duration (s) | Resolution | ratio |
First frame / reference images |
|---|---|---|---|---|
grok-imagine-video-1.5 |
1–15 | 480p / 720p / 1080p |
16:9 (default), 9:16, 1:1, 4:3, 3:4, 3:2, 2:3, 21:9, 5:2; with a first frame and no ratio, the frame's ratio is used |
Up to 1 |
minimax-h3-max, minimax-h3-max-turbo |
5–15 | 480p / 768p |
16:9 (default), 9:16, 1:1, 4:3, 3:4, 21:9; with a first frame you can pass adaptive (follow the frame, also the default when a first frame is given) |
Up to 2 (including first/last frame) |
minimax-h3 |
4–15 | Fixed at 768P | — | — |
minimax-h3-2k |
4–15 | Fixed at 2K | — | — |
- If
resolutionis omitted, the video is generated and billed at the default tier shown in the model catalog. Forminimax-h3/minimax-h3-2kthe resolution is set by the model name and anyresolutionyou pass has no effect. - These models do not accept reference videos, reference audio or
asset://references from the asset library; such requests are rejected with 400 and not billed. - A duration, resolution or ratio outside the ranges above is rejected with 400 at submission and not billed.
Wan 3.0 family
wan3.0-video, wan3.0-video-prime (fast edition) and the -lm models wan3.0-video-lm, wan3.0-video-prime-lm, wan3.0-video-pro-lm and wan3.0-video-pro-prime-lm are all submitted with POST /v1/video/generations and polled with the query endpoint above. On success status is completed; read the video link from video_url.
cURL
curl https://api.onefast.ai/v1/video/generations \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan3.0-video-lm",
"prompt": "The man slowly turns his head and smiles",
"duration": 5,
"resolution": "720p",
"first_frame_url": "https://example.com/portrait.jpg"
}'
| Model | Resolution | Smart duration (-1) |
Reference video |
|---|---|---|---|
wan3.0-video |
480p / 720p / 1080p |
— | Yes |
wan3.0-video-prime |
480p / 720p / 1080p(faster) |
— | Yes |
wan3.0-video-lm |
480p / 720p / 1080p |
Yes | Yes |
wan3.0-video-prime-lm |
480p / 720p / 1080p(faster) |
Yes | Yes |
wan3.0-video-pro-lm |
1080p / 2k / 4k |
Yes | Yes |
wan3.0-video-pro-prime-lm |
1080p / 2k / 4k(faster) |
Yes | Yes |
| Field | Description |
|---|---|
duration (or seconds) |
Integer seconds, 2–30, required. The -lm models also accept -1 (smart duration: the model picks the length) |
resolution |
See the table above; if omitted, the default tier listed in the model catalog is generated and billed |
ratio (or aspect_ratio) |
16:9 (text-to-video default), 9:16, 4:3, 1:1, 3:4; wan3.0-video / wan3.0-video-prime also accept 21:9. With a first frame you can pass adaptive (follow the first frame; also the default when a first frame is given) |
first_frame_url |
First-frame image URL (image also works) |
last_frame_url |
Last-frame image URL; requires a first frame |
image_urls |
Array of reference image URLs (people, objects) |
seed |
Random seed, 0–2147483647 |
generate_audio |
-lm models only: generate an audio track |
prompt_extend |
-lm models only: let the model expand the prompt |
video_urls |
Array of reference video URLs (video editing / replacing the main subject), up to 3; input video length is billed per second too |
- Smart duration billing: with
-1, 30 seconds are reserved at submission; on completion you are billed for the seconds actually generated and the difference is refunded, so your balance must cover 30 seconds. - Reference video billing: with reference videos, billed seconds = input video length + output length; 15 seconds per reference video are reserved at submission and settled against the actual length on completion.
- Reference audio and
asset://library references are not supported; the-lmmodels do not supportwatermark: true. Such requests return 400 immediately and are not billed. - Duration, resolution or ratio outside the ranges above returns 400 at submission and is not billed.
- Video links can be downloaded for 24 hours from submission — save them to your own storage promptly.
Billing
Billed per second: seconds actually generated × the model's unit price (rounded up, 1-second minimum). Failed tasks are refunded automatically.
Some models are priced per resolution tier (e.g. 480p / 720p / 1080p for grok-imagine-video-1.5, 480p / 768p for minimax-h3-max). The per-second price of each tier and the default tier are listed separately in the model catalog.
Failed tasks and error codes
When a task fails (status is failed), the query response tells you why and what to do. Failed tasks are never billed; any reserved amount is refunded automatically.
You can also review failures under Usage Logs in the console: filter by failed requests, or search by task ID (mvt-… / local-reserve-…) or request ID in the extra filters. The message there matches the API response and shows whether the charge was refunded.
- OpenAI video format (
/v1/video/generations/{id},/v1/videos/{id}):errorholds the reason anderror_codethe machine-readable code. Iferror_codeis absent, readerror. - Seedance (Volcengine Ark-compatible format):
error.messageholds the reason;error.codeis one of the codes below, or the official Ark code (InvalidParameter.*,*SensitiveContentDetected.*) when there is one. - If the submission itself is rejected, the same code and reason come back immediately in the error response.
error_code |
Meaning | What to do |
|---|---|---|
content_moderation_rejected |
The input image / video / text, or the generated result, was rejected by content moderation | Replace the material or revise the prompt. Resubmitting the same content will be rejected again |
video_download_failed |
A reference image / video URL could not be downloaded | Make sure the URL is publicly accessible: no login, no expired signature, no hotlink protection or IP restriction |
invalid_parameter / InvalidParameter |
A request parameter is invalid (the message says which one), e.g. an image_url that is too long because base64 was put in it |
Fix the parameter and resubmit |
video_processing_error / video_processing_timeout |
Temporary error or timeout on the model service | Resubmit as is; nothing needs to change |
video_submit_timeout |
The submission timed out and could not be confirmed | Resubmit as is |
concurrent_task_limit |
Too many tasks are running for this model | Wait for running tasks to finish, then resubmit |
service_temporarily_unavailable |
The service is temporarily unavailable | Retry later |
video_generation_failed |
Any other failure (the message includes the error code when there is one) | Retry; if it keeps failing, contact support with the task id |
Full example: submit and wait for the result
Python
import os, time, requests
BASE = "https://api.onefast.ai"
H = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
task = requests.post(f"{BASE}/v1/video/generations", headers=H, json={
"model": "grok-imagine-video",
"prompt": "A paper boat drifting on a rainy street, cinematic",
"duration": 5,
}).json()
task_id = task["id"]
while True:
r = requests.get(f"{BASE}/v1/video/generations/{task_id}", headers=H).json()
if r["status"] in ("completed", "succeeded"):
url = r.get("url") or r.get("video_url")
open("video.mp4", "wb").write(requests.get(url).content)
print("saved video.mp4")
break
if r["status"] == "failed":
raise RuntimeError(r.get("error"))
time.sleep(8)
Node.js
import fs from "node:fs";
const BASE = "https://api.onefast.ai";
const H = { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" };
const task = await (await fetch(`${BASE}/v1/video/generations`, {
method: "POST", headers: H,
body: JSON.stringify({ model: "grok-imagine-video", prompt: "A paper boat drifting on a rainy street, cinematic", duration: 5 }),
})).json();
for (;;) {
const r = await (await fetch(`${BASE}/v1/video/generations/${task.id}`, { headers: H })).json();
if (r.status === "completed" || r.status === "succeeded") {
const buf = Buffer.from(await (await fetch(r.url || r.video_url)).arrayBuffer());
fs.writeFileSync("video.mp4", buf);
console.log("saved video.mp4");
break;
}
if (r.status === "failed") throw new Error(JSON.stringify(r.error));
await new Promise((s) => setTimeout(s, 8000));
}
Poll every 5–10 seconds, and never more than once per second. Save the task ID right after submitting — you can only query by ID.