REST API
Call the MachGen REST API directly over HTTPS.
API is hosted at https://api.machgen.ai.
The general task lifecycle is always the same: submit a task, poll its status until it completes, then download the result.
Depending on the exact task, the task input may require specific fields. Please refer to the following example for details.
Provide your API key
Send your key (see Get Started) as a bearer token on every request:
# Key starts with `MGA_`
export MACHGEN_API_KEY=".."
curl -H "Authorization: Bearer $MACHGEN_API_KEY" https://api.machgen.ai/..Submit a task
POST /api/v0/generate with a JSON TaskInput describing the task. It returns
immediately with a task_id - it does not wait for generation. The body fields
differ per task type.
Check the following examples to get a quick start.
For detailed API input/output reference, refer to the API Reference page.
curl https://api.machgen.ai/api/v0/generate \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A red panda exploring a misty forest at dawn",
"model": "Wan2.2-A14B",
"task_type": "T2V",
"video_config": {"duration_secs": 5, "height": 720, "aspect_ratio": "16:9", "fps": 16, "infer_steps": 30}
}'Config values are model-specific
duration_secs, height, and aspect_ratio must be one of the values the
target model supports - the duration_secs: 5 and height above are representative,
not universal.
A value outside a model's grid is rejected at submit with HTTP 400.
For text-to-video with Seedance-2.0, Seedance-2.0-Fast,
Seedance-2.0-Mini, or Seedance-2.5, set video_config.aspect_ratio to
"adaptive" to let the model choose the framing from your prompt.
Seedance-2.5 can also make a 480p sample first. See Draft mode.
See Available Models for each model, and API Reference for the config fields.
The response carries the task_id you poll and download with:
{ "task_id": "t-abc123", ... }Check status and download
Poll GET /api/v0/tasks/{task_id} until status is COMPLETED or FAILED.
curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/tasks/t-abc123To skip polling, pass webhook_url (an HTTPS URL on a public host) with the
generate request. When the task finishes we POST this same status response to
it once, with the task id in the X-Task-Id header. If your endpoint does not
answer with a 2xx we retry for a while, so treat a repeat as the same event.
You can check whether each webhook was delivered under
Webhooks on your profile page.
{
"task_id": "t-abc123",
"status": "COMPLETED",
"metadata": {
"prompt": "...",
"height": 720,
"width": 1280,
"fps": 24,
"duration_secs": 5,
...
},
"task_output": {
"video": "https://api.machgen.ai/api/v0/assets/t-abc123"
}
}curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/assets/t-abc123 -o output.mp4A task moves through these statuses; COMPLETED and FAILED are terminal:
| Status | Terminal | Meaning |
|---|---|---|
PENDING | Accepted and queued, not yet started. | |
RUNNING | Generation in progress. | |
COMPLETED | yes | Generation completed; ready to download. |
FAILED | yes | Generation failed; see error_msg. |
Cancel a task
A queued task can be canceled before it starts. Cancel is best effort: if the
task is still PENDING, it stops there and the charge is refunded. Once it is
RUNNING the request is ignored and the task finishes normally. A multi-stage
task counts as started as soon as any stage has started.
curl -X POST -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/tasks/t-abc123/cancelThe response is the same status object returned by GET /api/v0/tasks/{task_id},
read after the cancel. A canceled task shows as FAILED with an error_msg
that says it was canceled; a task that had already started shows its current
status instead. Calling cancel on a finished task is safe and changes nothing.
{
"task_id": "t-abc123",
"status": "FAILED",
"error_msg": "The task was canceled before it completed. Any charge was refunded."
}If the task has a webhook_url, the webhook is still delivered, with this same
response.
Account status
GET /api/v0/billing/account returns your balance and how many of your tasks
are queued and running right now, so you can pace submissions against your
account's concurrency limit.
curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/billing/account{
"account_id": "acct-abc123",
"balance_micros": 12500000,
"autoreload_enabled": false,
"pending_tasks": 2,
"running_tasks": 1
}Errors
A FAILED status (above) reports a task that was accepted but could not be
generated. A request that is rejected before acceptance instead returns a non-2xx
HTTP status with a JSON body carrying a single detail string:
{ "detail": "Wan2.2-T2V-A14B does not support height=1080; allowed heights: [480, 720]" }| Status | Meaning | Example detail |
|---|---|---|
400 Bad Request | Invalid body: unknown model/task_type, missing config, or a shape/duration outside the model's grid. | Invalid model |
401 Unauthorized | Missing, malformed, or unknown API key. | Invalid API key |
403 Forbidden | Key is valid but the account may not use this feature or model. | The 'generate' feature is not enabled for your account. |
404 Not Found | No task with the given id (e.g. when polling). | Task t-abc123 not found |
429 Too Many Requests | Rate limit exceeded; retry later. | Too many submissions; please try again later. |
Sync mode
For self-hosted models you can skip the poll-and-download round trip and receive
the finished result inline. POST /api/v0/generate/sync takes the same JSON
TaskInput body, but the request blocks until the worker streams back the
encoded result and returns those bytes directly.
Sync mode is available for:
T2IandI2Itasks on self-hosted (non-passthrough) models, returningimage/jpeg;- selected self-hosted video surfaces that generate fast enough to answer within
the wait, returning
video/mp4. On a model with a prompt enhancer,POST /api/v0/generate/syncenhances by default exactly asPOST /api/v0/generatedoes - that call runs inside your wait, so send"enhance_prompt": falseif you would rather start generating sooner.
Everything else returns 400.
curl https://api.machgen.ai/api/v0/generate/sync \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "An isometric illustration of a cozy reading nook, soft lighting",
"model": "FLUX.2-dev",
"task_type": "T2I",
"image_config": {"width": 1024, "height": 1024}
}' -o output.jpgOn success the response is 200 OK with the raw encoded bytes as the body -
image/jpeg for an image task, video/mp4 for a video one. The
X-Machgen-Delivery-Mode header says which (sync-image or sync-video), and
X-Machgen-Task-Id carries the task id: the durable task may still be finalizing
when you receive the bytes, so use that id to poll status or fetch the canonical
asset later, exactly as with an async task.
If the result cannot be delivered inline - generation runs past the timeout, the
result is oversize, or an inline error occurs - the server falls back to the async
path and returns 202 Accepted with the normal GenerateResponse JSON body plus
an X-Machgen-Sync-Fallback-Reason header (timeout, oversize, or
inline_error).
A fallback is not a failure: the task continues on the async path and can be
polled and downloaded exactly like one submitted to POST /api/v0/generate.
Inline source images (sync only)
To skip the source-upload round trip on latency-sensitive I2I requests, the
sync endpoint also accepts inline base64 entries in src_image_urls using the
standard data-URL form:
{
"task_type": "I2I",
"src_image_urls": ["data:image/png;base64,iVBORw0KGgo..."]
}The server verifies the bytes at admission, stores a durable copy in your
upload space, and the task's metadata echoes a normal @input/... ref - after
acceptance an inline source is indistinguishable from an uploaded one. Each
inline image is capped at 1 MB of raw bytes (about 1.37 MB encoded - base64
inflates by about a third); larger images must be uploaded instead. The Python SDK
ships a size-guarded helper, machgen.client.inline_image_source(path), that
encodes a local file into this form.
Inline sources are accepted only on /v0/generate/sync;
POST /api/v0/generate rejects them with a 400.
Draft mode
Draft mode is a two-step workflow for Seedance-2.5. First generate a 480p
sample and check the scene, shots, and motion. When it looks right, start a
new task for the 1080p final. The final is a separate generation. It keeps the
sample's prompt, references, named subjects, duration, aspect ratio, seed, and
audio.
It is only available for Seedance-2.5 text-to-video, image-to-video, and
reference-to-video (T2V, I2V, R2V). Other models, and UPSCALE, reject
it.
Each step is billed as a normal video at that height: the sample at 480p, the final at 1080p. See Pricing for the current rates.
Send draft and draft_source_task_id on the JSON body of
POST /api/v0/generate. The Python client's TaskInput does not include
them, so a typed submit_task call drops both fields.
Generate the sample
Set draft to true and video_config.height to 480. Any other height
returns HTTP 400: Draft videos are 480p.
curl https://api.machgen.ai/api/v0/generate \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Seedance-2.5",
"task_type": "T2V",
"prompt": "A paper boat crossing a rain-soaked neon street at night",
"draft": true,
"video_config": {
"duration_secs": 5,
"height": 480,
"aspect_ratio": "16:9"
}
}'Image-to-video and reference-to-video use the same draft flag and the same
480p height. Pass src_image_urls (and subject_to_image_ids when the prompt
uses @ names) exactly as you would for a normal request.
The response is a task_id. Poll GET /api/v0/tasks/{task_id} until status
is COMPLETED. A sample sets metadata.draft to true. A normal video omits
it.
{
"task_id": "t-draft",
"status": "COMPLETED",
"metadata": {
"model": "Seedance-2.5",
"task_type": "T2V",
"height": 480,
"draft": true
}
}Generate the final
Start a new task. Set draft_source_task_id to the sample's task_id. Do not
set draft on this request.
prompt and video_config are still required on the body. The server
replaces the prompt, references, named subjects, duration, aspect ratio, seed,
and audio with the sample's, and renders 1080p. A fixed ratio is resized for
1080p, so 16:9 becomes 1920x1080. adaptive stays adaptive.
curl https://api.machgen.ai/api/v0/generate \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Seedance-2.5",
"task_type": "T2V",
"prompt": "A paper boat crossing a rain-soaked neon street at night",
"draft_source_task_id": "t-draft",
"video_config": {
"duration_secs": 5,
"height": 1080,
"aspect_ratio": "16:9"
}
}'The final is ready only when its own task reaches COMPLETED. Its
metadata.draft is omitted.
draft_source_task_id is the MachGen task id from the sample. Callers cannot
set the vendor's own task id. The sample must be a completed Seedance-2.5
draft that you own. Otherwise the request returns HTTP 400:
| Response | When |
|---|---|
Draft is only available for Seedance 2.5 video | draft: true on another model, or on a task type other than T2V, I2V, or R2V |
Draft videos are 480p | draft: true with a height other than 480 |
Draft task was not found | draft_source_task_id is missing, or it is not your task |
Final videos start from a Seedance 2.5 draft | The source is not a Seedance 2.5 draft |
The draft video is not ready | The sample is not COMPLETED |
Source images
For I2V, R2V, and I2I, every entry in src_image_urls must be a public
http(s):// URL. (On POST /api/v0/generate/sync only, an entry may instead
be an inline data:image/...;base64, URL - see
Inline source images.)
For I2V the list is positional: entry 0 is the start frame, and an
optional entry 1 is the end frame. To animate from a single image, pass one URL;
to pin both ends of a clip, pass two.
{
"task_type": "I2V",
"src_image_urls": ["https://example.com/first-frame.png", "https://example.com/last-frame.png"]
}An end frame is supported by Alibaba-Wan-3.0, the Seedance-2.0 family,
Kling-v3, LTX-2.3-Pro, Vidu-Q3-Turbo and Vidu-Q3-Pro. Every other model accepts one image only and
returns HTTP 400 for a second, rather than discarding it. A third image is
always a 400.
To animate towards a single image instead, mark it as the end frame with
keyframe_indices. The list is aligned 1:1 with src_image_urls: 0 is the
start frame and -1 the end frame, so [0], [0, -1] and [-1] are the
accepted values, and omitting it keeps the positional reading above. A lone
end frame is supported by MiniMax-H3; other models return HTTP 400 for it.
{
"task_type": "I2V",
"src_image_urls": ["https://example.com/last-frame.png"],
"keyframe_indices": [-1]
}F2F is folded into I2V
task_type: "F2F" is still accepted and behaves identically - the server maps it to a two-image I2V. Prefer I2V;
F2F remains only for older clients.
Reference video and audio
| Field | Max | Per-clip limits |
|---|---|---|
src_image_urls | 9 | JPG / PNG / WebP, 50 MB |
src_video_urls | 3 | MP4 / MOV, 2-15s each, 15s combined, 50 MB |
src_audio_urls | 3 | MP3 / WAV, 15s combined |
At least one image or video is required - audio alone returns HTTP 400.
Seedance-2.0-Fast and Seedance-2.0-Mini also cap the combined image, video,
and audio reference count at 12.
A prior MachGen generation can be referenced by task id in src_task_ids
instead of a URL: each id is routed to src_video_urls, src_image_urls, or
src_audio_urls by the source task's output type, appended after any URLs
already in that list. Subject maps (subject_to_*_ids) index the lists as
they stand after that routing.
A public http(s):// entry in src_video_urls (a signed link included) is
checked for reachability at submit time with a lightweight request; a link that
does not answer with success is rejected with HTTP 400
input not accessible: <url> (<reason>) before the task is admitted.
Name a reference with the matching subject map and address it in the prompt as
@name. Whitespace in a submitted name is normalized to _ together with its
matching prompt mention; names that collide after normalization return HTTP 400. A name may appear in only one map. Once any subject is named, every
unquoted @handle in the prompt must match a subject - the request is rejected
with HTTP 400 otherwise. To keep a literal @handle as plain prompt text (a
screen name, for example), quote it: "@handle".
curl https://api.machgen.ai/api/v0/generate \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "@alice dances to @beat in the style of @routine",
"model": "Seedance-2.0",
"task_type": "R2V",
"src_image_urls": ["https://example.com/alice.png"],
"src_video_urls": ["https://example.com/routine.mp4"],
"src_audio_urls": ["https://example.com/beat.mp3"],
"subject_to_image_ids": {"alice": [0]},
"subject_to_video_ids": {"routine": [0]},
"subject_to_audio_ids": {"beat": [0]},
"video_config": {"duration_secs": 5, "height": 720, "aspect_ratio": "16:9", "fps": 24}
}'A reference video can be read from an offset instead of its start with
reference_video_start_secs, aligned 1:1 with src_video_urls (as the list
stands after src_task_ids routing) and given in seconds. The offset must
fall inside the clip, and what remains after it is what the model sees, what
counts towards the combined duration limit, and what is billed. Omitted, every
clip is read from 0. A non-zero offset is supported by MiniMax-H3; other
models return HTTP 400 for it.
{
"task_type": "R2V",
"src_video_urls": ["https://example.com/a.mp4", "https://example.com/b.mp4"],
"reference_video_start_secs": [2.5, 0]
}Wan 3.0 file and webpage inputs
Alibaba-Wan-3.0 R2V can use one public HTTPS file or webpage URL in
place of ordinary image, video, and audio references:
{
"prompt": "Turn the creative brief into a cinematic product reveal",
"model": "Alibaba-Wan-3.0",
"task_type": "R2V",
"src_file_urls": ["https://example.com/creative-brief.pdf"],
"video_config": {
"duration_secs": 10,
"height": 1080,
"aspect_ratio": "adaptive",
"audio": true
}
}Use src_webpage_urls instead when the input is a webpage. Both lists accept
at most one URL. Direct URLs must use HTTPS, resolve to public addresses, and
return a successful response at submission time. A request must choose exactly
one input mode: ordinary media, one file, or one webpage. Mixing the modes
returns HTTP 400.
The Python client also accepts a local path in src_file_urls and uploads it
before submission. Browser clients can call POST /api/v0/upload first and use
the returned @input/<artifact_path> reference. Direct uploads are limited to
50 MB.
Request and response reference
The full field reference for the request body (TaskInput, VideoConfig,
ImageConfig) and the status response (TaskStatusResponse) lives on the
API Reference page.