Video Generation
RouterHub provides an asynchronous video generation API. One request shape serves every
supported upstream — currently Seedance (BytePlus ModelArk),
MiniMax H3, and Runway (Gen 4.5 & Aleph 2.0) —
selected with the model field. The workflow
follows a task-based pattern: submit a generation request, then poll
GET /v1/video/generations/{task_id} for results or provide a
callback_url to be notified when RouterHub observes a terminal state.
Valid values for resolution, duration, ratio, and media
role fields differ per model. Check
MiniMax H3 Constraints and
Runway Gen 4.5 & Aleph 2.0 Constraints before pointing existing
code at a MiniMax or Runway model.
Asynchronous API — Video generation takes time. The Create endpoint returns immediately with a task object. In the common case the initial status is queued. Poll the Get endpoint until status is terminal (succeeded, failed, or cancelled). You may also set callback_url to be notified when the task completes.
Overview
| Endpoint | Method | Description |
|---|---|---|
| /v1/video/generations | POST |
Create a new video generation task |
| /v1/video/generations | GET |
List your video generation tasks |
| /v1/video/generations/{task_id} | GET |
Get a specific task (poll for status/result) |
| /v1/video/generations/{task_id} | DELETE |
Delete (cancel) a video generation task |
| /v1/video/assets | POST |
Create (upload) a new media asset for use as reference |
| /v1/video/assets | GET |
List assets in your asset library |
| /v1/video/assets/{asset_id} | GET |
Get asset status and details |
| /v1/video/assets/sessions | POST |
Create a real-human verification session (H5 flow) |
| /v1/video/assets/sessions/callback | GET |
Platform inbound callback for H5 session redirect (not for client use) |
Available Models
| Model ID | Description | Capabilities |
|---|---|---|
| byteplus/seedance-2.0 | Seedance 2.0 — BytePlus's state-of-the-art video generation model | Text-to-video, Image-to-video, Video-to-video, Audio-driven |
| minimax/minimax-h3 | MiniMax H3 — open-weight multimodal generation model. Always outputs native stereo audio. | Text-to-video, Image-to-video (first/last frame), Reference-to-video (image / video / audio) |
| runway/gen-4-5 | Runway Gen 4.5 — high-fidelity text-to-video and image-to-video model. | Text-to-video, Image-to-video |
| runway/aleph-2-0 | Runway Aleph 2.0 — specialized video-to-video transformation and styling model. | Video-to-video (requires input video) |
Model-specific parameter constraints differ. See
MiniMax H3 Constraints and
Runway Gen 4.5 & Aleph 2.0 Constraints before switching a
request from one model to another — resolution values, allowed
ratio values, and duration rules are not interchangeable.
Task Status Lifecycle
| Status | Description | Terminal? |
|---|---|---|
| queued | Task accepted, waiting for GPU resources | No |
| running | Video is being generated | No |
| succeeded | Generation complete — video URL available in content |
Yes |
| failed | Generation failed — see error field for details |
Yes |
| cancelled | Task was cancelled via DELETE | Yes |
Create Video Generation
Submit a video generation task. The response returns immediately with a task object containing a unique id for polling.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string |
Yes | Model identifier. Currently supported: "byteplus/seedance-2.0", "minimax/minimax-h3", "runway/gen-4-5", "runway/aleph-2-0". |
| content | array |
Yes | Array of content items describing the video to generate (text prompts, reference images/videos/audio). Must contain at least 1 item. |
| duration | integer |
No | Video duration in seconds. Forwarded to upstream as-is — limits are decided by the upstream model, not by the gateway. A model may have a platform-side allowed range configured, in which case an out-of-range value returns 400 naming that range. If omitted, the provider default (5s for Gen 4.5) is used. Aleph 2.0 is always pre-authorized at 30s regardless of whether duration is supplied — the value is never sent upstream (output length is determined by the input video), and any client-supplied duration is ignored with a note; the terminal charge is reconciled against real consumption. Two validations still happen at the gateway: the value must be -1 (auto, supported by Seedance only) or a positive integer within model bounds, and an extremely large value that cannot be priced returns 400. |
| ratio | string |
No | Aspect ratio. Supported values: "16:9", "9:16", "1:1", "adaptive". Forwarded to upstream after trimming whitespace. Defaults to "16:9" for billing estimation when omitted. |
| resolution | string |
No | Output resolution. Common values are "720p", "1080p", and "4k". Defaults to "1080p" for billing estimation when omitted. |
| seed | integer |
No | Optional upstream seed value. Passed through to upstream when present. |
| framespersecond | integer |
No | Optional FPS hint forwarded as-is to upstream. Note the JSON field name is exactly framespersecond (no underscores). |
| generate_audio | boolean |
No | Optional upstream flag for generating audio. If omitted, RouterHub does not inject a default; upstream behavior applies. |
| callback_url | string |
No | Customer webhook URL (must be HTTPS). When the task reaches a terminal state, RouterHub POSTs the task JSON to this URL. Not returned in Get responses; use metadata for correlation IDs. |
| execution_expires_after | integer |
No | Requested execution timeout in seconds. If omitted, defaults to 7200 seconds (2 hours). |
| metadata | object |
No | Arbitrary JSON metadata attached to the task. Returned in all subsequent responses. |
Content Item Structure
Each element in the content array describes an input modality:
| Field | Type | Description |
|---|---|---|
| type | string |
Required. One of: "text", "image_url", "video_url", "audio_url" |
| role | string |
Optional role for media items. Accepted values: "reference_image" (for image_url), "reference_video" (for video_url), "reference_audio" (for audio_url).
minimax/minimax-h3 additionally accepts "first_frame" and "last_frame" on image_url items for first/last-frame generation. |
| text | string |
Text content (required when type is "text"). This is your generation prompt. |
| image_url | object |
Image reference (required when type is "image_url"). Contains {"url": "..."} or {"asset_id": "..."}. Note: asset_id is supported only by Seedance models. Other models (MiniMax H3, Runway Gen 4.5) accept direct public image URLs or base64 data URIs, and allow direct upload/usage of real-human images without asset verification sessions. |
| video_url | object |
Video reference (required when type is "video_url"). Contains {"url": "..."} or {"asset_id": "..."}. Note: asset_id is supported only by Seedance models; other models require direct public URLs. |
| audio_url | object |
Audio reference (required when type is "audio_url"). Contains {"url": "..."} or {"asset_id": "..."}. Note: asset_id is supported only by Seedance models; other models require direct public URLs. |
Generation Modes
These are the common Seedance request shapes supported by the API:
| Mode | Content Configuration | Description |
|---|---|---|
| Text-to-Video | [{type:"text", text:"..."}] |
Generate video from a text prompt alone. |
| Image-to-Video | [{type:"text", text:"..."}, {type:"image_url", role:"reference_image", image_url:{url:"..."}}] |
Animate a reference image with a text prompt. The media role is optional but only reference_image is preserved if provided. |
| Video-to-Video | [{type:"text", text:"..."}, {type:"video_url", role:"reference_video", video_url:{url:"..."}}] |
Transform or extend a reference video. Only reference_video is preserved if a role is provided. |
| Audio-driven | [{type:"text", text:"..."}, {type:"image_url", role:"reference_image", ...}, {type:"audio_url", role:"reference_audio", audio_url:{url:"..."}}] |
Generate video synchronized to reference audio. Audio cannot be the only reference input — you must also include a reference_image or reference_video alongside the audio reference. |
Parameter Constraints
| Constraint | Details |
|---|---|
| duration | Must be between 4 and 15 seconds (inclusive). Outside this range returns 400. |
| ratio | Supported: "16:9", "9:16", "1:1", "adaptive". |
| resolution | Common values: "720p", "1080p", "4k". Unknown resolutions may be rejected by billing estimation. |
| reference_audio | Audio cannot be the only reference input and must be combined with reference_image or reference_video. |
The table above applies to byteplus/seedance-2.0. For
minimax/minimax-h3 see the next section — its values are different
and are not a superset.
MiniMax H3 Constraints
This section covers the whole MiniMax H3 family — both
minimax/minimax-h3 and minimax/minimax-h3-max.
They accept the same request shape as other video models, but several parameters have
different valid values, and some of those values differ between the two models
(see H3 vs H3 Max). Requests that violate these return
400 with an explanatory message rather than being silently adjusted.
| Parameter | Details |
|---|---|
| resolution | Required. Valid values depend on the model:
minimax/minimax-h3 accepts "768P" and "2K";
minimax/minimax-h3-max accepts "480P" and "768P"
("2K" is not available on H3 Max).
Values valid for other model families ("720p", "1080p",
"4k") return 400. Matching is case-insensitive. |
| duration | Required. Integer seconds, range depends on the model:
minimax/minimax-h3 accepts 4–15;
minimax/minimax-h3-max accepts 5–15
(4 seconds is not available on H3 Max). |
| role | Labels what each media item is for. Valid values for image_url:
"first_frame", "last_frame", "reference_image".
Required once the request contains more than one image. With exactly one image it may be omitted, in which case the upstream treats it as "first_frame".
At most one "first_frame" and at most one "last_frame"
per request; the two may be combined. Violations return 400 naming both
conflicting indices, e.g.
content[2]: role "first_frame" may appear at most once (already used at content[1]).
minimax/minimax-h3-max supports first/last frame only —
reference roles are rejected upstream (MiniMax lists reference generation as
forthcoming for that model). |
| ratio | Supported: "16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive".
For text-only generation (no image / video / audio item in content): "adaptive" is not accepted, and omitting ratio defaults to "16:9".
"adaptive" is only usable when at least one media item is present. |
| generate_audio | H3 always produces native stereo audio and cannot be turned off. Sending false returns 400 rather than being ignored — otherwise you would receive audio you explicitly opted out of. |
| seed | Not supported. Sending it returns 400. H3 results are not reproducible from a seed, so accepting and ignoring the field would create a false expectation. |
| framespersecond | Output is fixed at 24 fps. 24 is accepted; any other value returns 400. |
| image_url / video_url / audio_url | Only {"url": "..."} is supported. {"asset_id": "..."} returns 400 — H3 does not resolve platform asset IDs. |
| content | Must contain at least one non-empty text item (max 7,000 characters), in every generation mode including image-to-video. |
| draft / service_tier / execution_expires_after | Not supported upstream. These are dropped and reported in the task's adjustment notes; they do not fail the request. |
Media limits
| Input | Limit |
|---|---|
| Images | JPG, JPEG, PNG, WEBP, HEIC, HEIF. 30 MB per file.
Width and height each in [256, 5760] px; aspect ratio (width / height) in [0.4, 2.5]. Counts: first frame ≤ 1, last frame ≤ 1, reference images ≤ 9. |
| Videos | MP4 or MOV; video H.264/AVC or H.265/HEVC, audio AAC or MP3.
Up to 3 clips, 2–15 s each, 15 s combined. 50 MB per file.
Width and height each in [256, 5760] px; aspect ratio in [0.4, 2.5]; frame rate in [23.976, 60]. Reference scenario only. |
| Audio | WAV or MP3. Up to 3 clips, 2–15 s each, 15 s combined. 15 MB per file.
Reference scenario only. |
| Mixed | Up to 12 media files total across all types.
Whole request body ≤ 64 MB — use public URLs for large files rather than inlining them. |
H3 vs H3 Max
minimax/minimax-h3-max is a speed-optimized variant of H3 (post-trained by
fal.ai on MiniMax H3). It takes the same request shape — only the
valid values differ, plus one response difference worth planning for.
minimax/minimax-h3 |
minimax/minimax-h3-max |
|
|---|---|---|
| resolution | 768P, 2K |
480P, 768P |
| duration | 4–15 s | 5–15 s |
| Generation modes | Text-to-video, image-to-video (first / last frame), reference-to-video | Text-to-video and image-to-video (first / last frame) only |
| Typical latency | Minutes (a 4 s clip took ~90 s in our testing) | Seconds (a 5 s clip took ~5 s in our testing) |
usage.completion_tokens |
The upstream's real token count (e.g. 130196 for a 4-second 768P clip) |
Always 0 — the upstream reports no token
figure for this model, and the gateway does not substitute a derived one |
If you read usage.completion_tokens, expect 0 from
H3 Max — the field is present, not missing. The usage object
is always returned for a succeeded task, and both token fields are always
serialized, so you will receive {"completion_tokens": 0, "total_tokens": 0}
rather than an absent key. Test for == 0, not for the key's existence.
Billing is unaffected — both models are charged on generated seconds, not tokens,
and cost is populated identically for both. What breaks is client code
written against H3's response that divides by the token count, or asserts it is non-zero.
Behaviors worth knowing
- Media order is preserved exactly as submitted. H3 prompts reference
inputs positionally (
"@Image 1 is the character, @Image 2 is the background"), so the gateway never reorders or dropscontentitems. If an item is malformed, the request fails with the offending index (e.g.content[1]: url must not be empty) instead of dropping it — dropping would shift every later index and silently break your prompt references. - Frame control and reference mode are mutually exclusive. A request may
use
"first_frame"/"last_frame", or the reference roles ("reference_image"/"reference_video"/"reference_audio") — never both. Mixing them returns400: reference mode cannot be mixed with first_frame/middle_frame/last_frame; choose one.
Note thatmiddle_frameappears in that upstream message but is not currently usable on either model (verified 2026-09-07):minimax/minimax-h3rejects it withrole="middle_frame" invalid for type="image_url", andminimax/minimax-h3-maxrejects it withmodel MiniMax-H3-Max does not support middle frames, use MiniMax-H3 instead— which points at a model that also rejects it. Do not build againstmiddle_framebased on that error text alone. - Some media problems surface only after the task is created. Parameter
errors (bad
role, unsupportedduration) are rejected synchronously on submit. Problems that require actually reading the file — an image outside the[0.4, 2.5]aspect range, for instance — are reported asynchronously: you receive a task id with statusrunning, and the task then moves tofailedwith a message such ascontent[3].image_url: invalid param: image aspect ratio 2.720 (=1708/628), expected in [0.4, 2.5].
These failures are not billed — no usage is reported and the pre-authorization is released. Do not treat "task created" as "inputs accepted". - One validation error at a time. The upstream stops at the first invalid parameter, so a request with several problems surfaces them one per attempt.
- Tasks with a reference video are pre-authorized at the 15-second maximum. The input video's real duration is not known at submit time, so the hold is calculated against the upper bound. The final charge uses the actual reported duration and the difference is released.
- No progress percentage. Status moves
queued→running→succeeded/failed/cancelledwith no intermediate progress value. - Prompt quality matters more than for other models. H3 responds to structured prompts (reference-asset instructions, core concept, shot-by-shot description). Short prompts with no reference assets are a documented cause of poor results — see MiniMax's official prompting guide.
Runway Gen 4.5 & Aleph 2.0 Constraints
Runway models (runway/gen-4-5 and runway/aleph-2-0) are hosted via RunwayML. They use the standard RouterHub asynchronous task interface but follow Runway-specific input constraints and parameter bounds.
| Parameter / Feature | Runway Gen 4.5 (runway/gen-4-5) |
Runway Aleph 2.0 (runway/aleph-2-0) |
|---|---|---|
| Primary Modality | Text-to-Video, Image-to-Video | Video-to-Video (stylization & transformation) |
| Media Input Requirements | Optional reference image in content (image_url). Must be a publicly accessible HTTP(S) URL or base64 data URI. BytePlus asset_id is not supported. |
Required: Exactly one reference video in content (video_url). Must be a publicly accessible HTTP(S) URL or base64 data URI. |
| duration | Integer, 2–10 seconds inclusive (defaults to 5). |
Optional. If supplied it must be an integer 1–30 seconds, but the value is ignored: Aleph 2.0 inherits the input video's duration and duration is never sent to upstream creation. The task is always pre-authorized at the 30s maximum and reconciled at settlement. A client-supplied duration produces a dropped-parameter note in the response. |
| ratio | Pixel resolution ratios: "1280:720" (16:9), "720:1280" (9:16), "1104:832" (4:3), "832:1104" (3:4), "960:960" (1:1). Defaults to "1280:720". |
Target aspect ratios: "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9". Passed as targetAspectRatio. |
| seed | Supported: integer from 0 to 4294967295. |
Supported: integer from 0 to 4294967295. |
| prompt length | Text item in content must be at most 1,000 characters. |
Text item in content must be at most 1,000 characters. |
| callback_url / webhooks | Runway upstream is polling-only. RouterHub automatically polls the task until completion and dispatches the standard webhook notification to your callback_url once a terminal state is reached. You can also actively poll GET /v1/video/generations/{task_id}. |
|
Runway Key Behaviors & Tips
- Direct Public URLs: Runway models fetch media assets directly from upstream servers. Always provide accessible public
http(s)://URLs or data URIs. Platform internal asset IDs (e.g.,asset_idorasset://) are rejected with400. - Native Output URL: Upon task completion (
succeeded), the native Runway MP4 download URL is returned directly incontent[0].video_url.url. - Automatic Endpoint Selection: The gateway automatically maps your request to Runway's Text-to-Video, Image-to-Video, or Video-to-Video endpoints depending on the model and inputs provided.
Example: Text-to-Video
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "byteplus/seedance-2.0",
"content": [
{
"type": "text",
"text": "A golden retriever running through a field of sunflowers at sunset, cinematic 4K, slow motion"
}
],
"duration": 10,
"ratio": "16:9",
"resolution": "1080p"
}'import requests
resp = requests.post(
"https://api.routerhub.ai/v1/video/generations",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "byteplus/seedance-2.0",
"content": [
{
"type": "text",
"text": "A golden retriever running through a field of sunflowers at sunset, cinematic 4K, slow motion"
}
],
"duration": 10,
"ratio": "16:9",
"resolution": "1080p",
},
)
task = resp.json()
print(task["id"], task["status"])
# → "cgt-20260804101917-tzl28" "queued"Response (200 OK)
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "queued",
"metadata": {}
}Example: Image-to-Video
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "byteplus/seedance-2.0",
"content": [
{
"type": "text",
"text": "The character slowly turns their head and smiles at the camera"
},
{
"type": "image_url",
"role": "reference_image",
"image_url": {
"url": "https://example.com/portrait.jpg"
}
}
],
"duration": 5,
"ratio": "9:16",
"resolution": "1080p"
}'Example: With Audio and Callback
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "byteplus/seedance-2.0",
"content": [
{
"type": "text",
"text": "A musician playing piano in a dimly lit jazz club"
},
{
"type": "image_url",
"role": "reference_image",
"image_url": {
"url": "https://example.com/pianist-photo.jpg"
}
},
{
"type": "audio_url",
"role": "reference_audio",
"audio_url": {
"url": "https://example.com/jazz-piano.mp3"
}
}
],
"generate_audio": true,
"duration": 15,
"ratio": "16:9",
"callback_url": "https://myapp.example.com/webhooks/video-done",
"metadata": {"project": "music-video", "scene": 3}
}'Example: Runway Gen 4.5 (Image-to-Video)
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "runway/gen-4-5",
"content": [
{
"type": "text",
"text": "Camera slowly pans up as gentle wind blows through the trees"
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/forest-landscape.jpg"
}
}
],
"duration": 5,
"ratio": "1280:720",
"seed": 42
}'Example: Runway Aleph 2.0 (Video-to-Video)
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "runway/aleph-2-0",
"content": [
{
"type": "text",
"text": "Transform this video into a vibrant cyberpunk anime aesthetic with neon glow"
},
{
"type": "video_url",
"video_url": {
"url": "https://example.com/street-walking.mp4"
}
}
],
"ratio": "16:9",
"seed": 12345
}'Get Video Generation
Retrieve the current status and result of a video generation task. Poll this endpoint until status reaches a terminal state (succeeded, failed, or cancelled).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| task_id | string |
The unique task identifier returned by the Create endpoint. |
Example Request
curl https://api.routerhub.ai/v1/video/generations/cgt-20260804101917-tzl28 \
-H "Authorization: Bearer $ROUTERHUB_API_KEY"import time
import requests
task_id = "cgt-20260804101917-tzl28"
headers = {"Authorization": "Bearer YOUR_API_KEY"}
# Polling loop
while True:
resp = requests.get(
f"https://api.routerhub.ai/v1/video/generations/{task_id}",
headers=headers,
)
task = resp.json()
print(f"Status: {task['status']}")
if task["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(5) # poll every 5 seconds
# On success, content contains the video URL
if task["status"] == "succeeded":
print("Video URL:", task["content"]["video_url"])
elif task["status"] == "failed":
print("Failed:", task["error"])Note: The content.video_url is a pre-signed URL with a time-limited expiry (typically 24 hours). Download the video promptly or poll the Get endpoint again to obtain a fresh URL.
Response: Running
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "running",
"updated_at": 1779767414
}Response: Succeeded
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "succeeded",
"updated_at": 1779767585,
"completed_at": 1779767585,
"content": {
"video_url": "https://ark-acg-ap-southeast-1.tos-ap-southeast-1.volces.com/dreamina-seedance-2-0/example.mp4?X-Tos-Algorithm=TOS4-HMAC-SHA256&..."
},
"usage": {
"completion_tokens": 108900,
"total_tokens": 108900
},
"resolution": "720p",
"duration": 5,
"seed": 8598,
"metadata": {}
}Note: The lifetime of the returned media URL differs by upstream, so treat it as opaque and download promptly in all cases.
For Seedance models it is a pre-signed, time-limited URL (usually 24 hours) — call Get again for a fresh one if it expires.
For MiniMax models it is a stable public path on MiniMax's CDN, with no signature and no expiry observed; re-querying returns the identical URL. Note this also means the URL is publicly reachable by anyone who has it — do not treat it as access-controlled.
The usage block is only returned by Get/List, not by the customer callback body.
Response: Failed
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "failed",
"updated_at": 1779767445,
"completed_at": 1779767445,
"error": {
"code": "content_policy_violation",
"message": "The prompt was rejected due to content policy.",
"type": "video_generation_error",
"param": null
}
}Error type: The error.type field is always "video_generation_error" for failed/cancelled tasks. The error.code identifies the specific failure (e.g. content_policy_violation, video_timeout).
List Video Generations
List all non-deleted video generation tasks for the authenticated API key. Results are ordered by created_at DESC, task_id DESC and support cursor-based pagination.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer |
20 | Number of tasks to return. Omitted or 0 becomes 20. Values above 100 are capped at 100. Negative or non-integer values return 400. |
| cursor | string |
— | Opaque pagination cursor from a previous response's next_cursor. |
Example Request
curl "https://api.routerhub.ai/v1/video/generations?limit=5" \
-H "Authorization: Bearer $ROUTERHUB_API_KEY"Response
{
"object": "list",
"data": [
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "succeeded",
"completed_at": 1779767585
},
{
"id": "cgt-20260804101349-cd74q",
"object": "video.generation.task",
"created": 1779767229,
"model": "byteplus/seedance-2.0",
"status": "running"
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOC0wNFQxMDowMzo0OVoiLCJ0IjoiY2d0LTIwMjYwODA0MTAxMzQ5LWNkNzRxIn0"
}Delete Video Generation
Delete a video generation task. The task is cancelled and removed from your task list.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| task_id | string |
The task identifier to delete. |
Example Request
curl -X DELETE https://api.routerhub.ai/v1/video/generations/cgt-20260804101917-tzl28 \
-H "Authorization: Bearer $ROUTERHUB_API_KEY"Response (200 OK)
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"deleted": true
}Webhook Callback
If you provide a callback_url in the Create request, RouterHub will POST the task JSON to that URL when the task reaches a terminal state (succeeded, failed, or cancelled).
Registration
To register a webhook, simply include callback_url in your Create request body:
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "byteplus/seedance-2.0",
"content": [
{"type": "text", "text": "A cat playing with a ball of yarn"}
],
"duration": 5,
"ratio": "16:9",
"callback_url": "https://myapp.example.com/webhooks/video-done"
}'Requirements: The callback_url must be a valid https:// URL. It is stored at task creation time and is not returned in subsequent Get responses. Use the metadata field for correlation IDs if you need to match callbacks to your internal records.
Callback Payload
When the task reaches a terminal state, RouterHub sends an HTTP POST to your callback_url with the following characteristics:
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Body | Task JSON object (same as Get response, without the usage field) |
| Authentication | None — RouterHub does not send authentication headers or signatures. Verify the id field against your task records. |
Callback body vs. Get body: The callback POST body is the task JSON without the usage field. To obtain the real token consumption (usage.completion_tokens / usage.total_tokens), call GET /v1/video/generations/{task_id} after receiving the callback.
Callback Request Example
Here is what RouterHub sends to your server when a task succeeds:
POST /webhooks/video-done HTTP/1.1
Host: myapp.example.com
Content-Type: application/json
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "succeeded",
"completed_at": 1779767585,
"content": {
"video_url": "https://ark-acg-ap-southeast-1.tos-ap-southeast-1.volces.com/dreamina-seedance-2-0/example.mp4?X-Tos-Algorithm=TOS4-HMAC-SHA256&..."
},
"resolution": "720p",
"duration": 5,
"seed": 8598,
"metadata": {"project": "music-video", "scene": 3}
}And when a task fails:
POST /webhooks/video-done HTTP/1.1
Host: myapp.example.com
Content-Type: application/json
{
"id": "cgt-20260804101917-tzl28",
"object": "video.generation.task",
"created": 1779767402,
"model": "byteplus/seedance-2.0",
"status": "failed",
"completed_at": 1779767445,
"error": {
"code": "content_policy_violation",
"message": "The prompt was rejected due to content policy.",
"type": "video_generation_error",
"param": null
},
"metadata": {}
}Callback Response
Your server should respond with HTTP 200 to acknowledge receipt. RouterHub treats the following response codes as follows:
| Response Status | Behavior |
|---|---|
| 2xx | Delivery successful, no retry |
| 4xx | Client error, not retried — check your endpoint configuration |
| 5xx | Server error, retried with backoff |
| Timeout / Network error | Retried with backoff |
Retry Policy
RouterHub retries failed deliveries (5xx or network errors) up to 3 total attempts with exponential backoff:
| Attempt | Timing (relative to first attempt) | Backoff before this attempt |
|---|---|---|
| 1 (initial) | T = 0s | — |
| 2 (retry 1) | T + 1s | 1s |
| 3 (retry 2) | T + 4s | 3s |
Each attempt has a 30-second timeout. If all 3 attempts fail, the callback is abandoned. You can still retrieve the final task state via GET /v1/video/generations/{task_id} at any time.
Webhook Handler Example
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post("/webhooks/video-done")
def handle_video_callback():
task = request.get_json()
task_id = task["id"]
status = task["status"]
if status == "succeeded":
video_url = task["content"]["video_url"]
print(f"Task {task_id} succeeded: {video_url}")
# Download the video (URL is time-limited, typically 24h)
# ...
elif status == "failed":
error = task.get("error", {})
print(f"Task {task_id} failed: {error.get('message')}")
elif status == "cancelled":
print(f"Task {task_id} was cancelled")
# Respond 200 to acknowledge receipt
return jsonify({"ok": True}), 200const express = require("express");
const app = express();
app.use(express.json());
app.post("/webhooks/video-done", (req, res) => {
const task = req.body;
const { id, status } = task;
if (status === "succeeded") {
const videoUrl = task.content.video_url;
console.log(`Task ${id} succeeded: ${videoUrl}`);
// Download the video (URL is time-limited, typically 24h)
} else if (status === "failed") {
const error = task.error || {};
console.log(`Task ${id} failed: ${error.message}`);
} else if (status === "cancelled") {
console.log(`Task ${id} was cancelled`);
}
// Respond 200 to acknowledge receipt
res.json({ ok: true });
});
app.listen(3000);Complete Workflow Example
Here's a full Python example that creates a video, polls for completion, and downloads the result:
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.routerhub.ai"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# Step 1: Create the video generation task
create_resp = requests.post(
f"{BASE_URL}/v1/video/generations",
headers=headers,
json={
"model": "byteplus/seedance-2.0",
"content": [
{"type": "text", "text": "A serene ocean wave crashing on a rocky shore at golden hour, 4K cinematic"},
{
"type": "image_url",
"role": "reference_image",
"image_url": {"url": "https://example.com/beach-reference.jpg"},
},
],
"duration": 10,
"ratio": "16:9",
"resolution": "1080p",
"generate_audio": True,
},
)
create_resp.raise_for_status()
task = create_resp.json()
task_id = task["id"]
print(f"Task created: {task_id} (status: {task['status']})")
# Step 2: Poll until terminal state
while task["status"] not in ("succeeded", "failed", "cancelled"):
time.sleep(5)
poll_resp = requests.get(f"{BASE_URL}/v1/video/generations/{task_id}", headers=headers)
poll_resp.raise_for_status()
task = poll_resp.json()
print(f" Status: {task['status']}")
# Step 3: Handle result
if task["status"] == "succeeded":
video_url = task["content"]["video_url"]
print(f"Video URL: {video_url}")
# Download the video (URL is time-limited, typically 24h)
video_data = requests.get(video_url).content
with open("output.mp4", "wb") as f:
f.write(video_data)
print("Saved to output.mp4")
elif task["status"] == "failed":
print(f"Generation failed: {task['error']['message']}")
elif task["status"] == "cancelled":
print("Task was cancelled")Error Responses
Error responses use the same top-level OpenAI-style error envelope as the rest of RouterHub. The exact type string varies by code path, so the table below focuses on status and trigger conditions that are confirmed in the implementation.
{
"error": {
"message": "content must contain at least one item",
"type": "invalid_request_error",
"param": null,
"code": null
}
}| HTTP Status | Cause |
|---|---|
| 400 | Invalid request body, missing model, empty content, invalid list cursor, invalid list limit, missing task_id, or duration outside 4–15s. |
| 401 | Missing or invalid API key |
| 402 | Insufficient credit balance for prepaid accounts |
| 404 | Unsupported model on create, or task_id not found for get/delete |
| 413 | Request body too large |
| 429 | Rate limit exceeded during create |
| 503 | Service unavailable or persistence failure |
Video Assets
The Video Assets API allows you to upload media files (images, videos, audio) to the asset library for use as references in video generation. Assets are uploaded via URL, processed and moderated by upstream, then become available for use in generation requests. Each organization has an asset group; assets are organized within that group.
Seedance-Exclusive Feature: The Video Assets API (/v1/video/assets/*) and asset ID referencing (asset_id or asset://{asset_id}) are only supported by BytePlus Seedance models. Other models (such as MiniMax H3 and Runway Gen 4.5 / Aleph 2.0) do not use the asset library; they accept direct public HTTP(S) image/video URLs or base64 data URIs. For real-human video generation, other models do not require asset verification sessions and allow directly submitting public portrait image URLs.
| Endpoint | Method | Description |
|---|---|---|
| /v1/video/assets | POST |
Create (upload) a new asset |
| /v1/video/assets | GET |
List assets in your organization's asset library |
| /v1/video/assets/{asset_id} | GET |
Get asset status and details |
Asset Status Lifecycle
| Status | Description |
|---|---|
| processing | Asset uploaded and being processed/moderated by upstream |
| active | Asset is ready for use in video generation requests |
| failed | Processing or moderation failed — see error field for details |
Create Asset
Upload a media file by providing its HTTPS URL. The asset will be processed and moderated by the upstream service. The response returns immediately with the asset ID and a processing status.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string |
Yes | HTTPS URL of the media file to upload. Must be a valid https:// URL. |
| asset_type | string |
Yes | Type of the asset. Must be one of: "Image", "Video", "Audio" (case-sensitive). |
| group_id | string |
No | Optional asset group identifier. If omitted, RouterHub resolves the organization's default asset group automatically. |
| name | string |
No | Optional human-readable name for the asset. Maximum 64 characters. |
Example Request
curl https://api.routerhub.ai/v1/video/assets \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/my-reference-image.jpg",
"asset_type": "Image",
"name": "Beach portrait reference"
}'import requests
resp = requests.post(
"https://api.routerhub.ai/v1/video/assets",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"url": "https://example.com/my-reference-image.jpg",
"asset_type": "Image",
"name": "Beach portrait reference",
},
)
asset = resp.json()
print(asset["id"], asset["status"])
# → "asset-20260804100000-abc12" "processing"Response (200 OK)
{
"id": "asset-20260804100000-abc12",
"object": "video.asset",
"status": "processing"
}Get Asset
Retrieve the current status and details of an asset. Poll this endpoint to check when an asset becomes active and ready for use in video generation requests.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| asset_id | string |
The unique asset identifier returned by the Create endpoint. |
Example Request
curl https://api.routerhub.ai/v1/video/assets/asset-20260804100000-abc12 \
-H "Authorization: Bearer $ROUTERHUB_API_KEY"Response: Processing
{
"id": "asset-20260804100000-abc12",
"object": "video.asset",
"name": "Beach portrait reference",
"asset_type": "Image",
"status": "processing",
"created_at": "2026-08-04T10:00:00Z",
"updated_at": "2026-08-04T10:00:00Z"
}Response: Active
{
"id": "asset-20260804100000-abc12",
"object": "video.asset",
"name": "Beach portrait reference",
"url": "https://cdn.example.com/assets/processed_abc12.jpg",
"asset_type": "Image",
"group_id": "grp-20260804100000-xyz",
"status": "active",
"created_at": "2026-08-04T10:00:00Z",
"updated_at": "2026-08-04T10:00:15Z"
}Response: Failed
{
"id": "asset-20260804100000-abc12",
"object": "video.asset",
"name": "Beach portrait reference",
"asset_type": "Image",
"status": "failed",
"error": {
"code": "moderation_rejected",
"message": "Asset rejected during content moderation"
},
"created_at": "2026-08-04T10:00:00Z",
"updated_at": "2026-08-04T10:00:20Z"
}List Assets
List assets in your organization's asset library. Supports page-based pagination and optional filtering by group_id.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer |
1 | Page number (1-based). Non-positive values default to 1. |
| limit | integer |
10 | Number of assets per page. Values above 100 are capped at 100. |
| group_id | string |
— | Optional asset group filter. If omitted, the organization's default group is used. |
Example Request
curl "https://api.routerhub.ai/v1/video/assets?page=1&limit=10" \
-H "Authorization: Bearer $ROUTERHUB_API_KEY"Response
{
"object": "list",
"items": [
{
"id": "asset-20260804100000-abc12",
"name": "Beach portrait reference",
"url": "https://cdn.example.com/assets/processed_abc12.jpg",
"asset_type": "Image",
"group_id": "grp-20260804100000-xyz",
"status": "active",
"created_at": "2026-08-04T10:00:00Z",
"updated_at": "2026-08-04T10:00:15Z"
}
],
"total_count": 1,
"page_number": 1,
"page_size": 10
}Asset Error Responses
| HTTP Status | Cause |
|---|---|
| 400 | Missing or invalid url (must be HTTPS), missing or invalid asset_type (must be Image/Video/Audio), name exceeds 64 characters, missing asset_id in path |
| 401 | Missing or invalid API key |
| 404 | Asset not found (wrong asset_id or asset belongs to a different organization) |
| 413 | Request body too large |
| 503 | Asset service unavailable or not configured |
Using Assets in Video Generation
Once an asset reaches active status, use the asset://{asset_id} protocol URL (or the asset_id field) in your video generation request's media fields. The gateway resolves the asset reference automatically:
# Step 1: Create an image asset
curl https://api.routerhub.ai/v1/video/assets \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/my-portrait.jpg",
"asset_type": "Image",
"name": "Portrait reference"
}'
# → {"id":"asset-20260804100000-abc12","object":"video.asset","status":"processing"}
# Step 2: Poll until active
curl https://api.routerhub.ai/v1/video/assets/asset-20260804100000-abc12 \
-H "Authorization: Bearer $ROUTERHUB_API_KEY"
# → {"id":"asset-20260804100000-abc12","object":"video.asset","status":"active",...}
# Step 3: Use asset:// protocol URL in video generation
curl https://api.routerhub.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "byteplus/seedance-2.0",
"content": [
{
"type": "text",
"text": "The person slowly turns their head and smiles warmly at the camera"
},
{
"type": "image_url",
"role": "reference_image",
"image_url": {
"url": "asset://asset-20260804100000-abc12"
}
}
],
"duration": 5,
"ratio": "9:16"
}'import time
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.routerhub.ai"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# Step 1: Upload an image asset
asset_resp = requests.post(
f"{BASE_URL}/v1/video/assets",
headers=headers,
json={
"url": "https://example.com/my-portrait.jpg",
"asset_type": "Image",
"name": "Portrait reference",
},
)
asset_resp.raise_for_status()
asset_id = asset_resp.json()["id"]
print(f"Asset created: {asset_id}")
# Step 2: Poll until the asset is active
while True:
get_resp = requests.get(f"{BASE_URL}/v1/video/assets/{asset_id}", headers=headers)
get_resp.raise_for_status()
asset = get_resp.json()
print(f" Asset status: {asset['status']}")
if asset["status"] == "active":
break
elif asset["status"] == "failed":
raise RuntimeError(f"Asset failed: {asset.get('error', {}).get('message')}")
time.sleep(3)
print(f"Asset ready: {asset_id}")
# Step 3: Use asset:// protocol URL in video generation
gen_resp = requests.post(
f"{BASE_URL}/v1/video/generations",
headers=headers,
json={
"model": "byteplus/seedance-2.0",
"content": [
{
"type": "text",
"text": "The person slowly turns their head and smiles warmly at the camera",
},
{
"type": "image_url",
"role": "reference_image",
"image_url": {"url": f"asset://{asset_id}"},
},
],
"duration": 5,
"ratio": "9:16",
},
)
gen_resp.raise_for_status()
task = gen_resp.json()
print(f"Video task created: {task['id']} (status: {task['status']})")
# Step 4: Poll video generation until complete
while task["status"] not in ("succeeded", "failed", "cancelled"):
time.sleep(5)
poll_resp = requests.get(f"{BASE_URL}/v1/video/generations/{task['id']}", headers=headers)
poll_resp.raise_for_status()
task = poll_resp.json()
print(f" Video status: {task['status']}")
if task["status"] == "succeeded":
print("Video URL:", task["content"]["video_url"])Key point: Use the asset://{asset_id} protocol URL (or the asset_id field on the media ref) to reference uploaded assets in video generation requests. The gateway resolves asset references to the underlying processed media automatically. You can also use direct HTTPS URLs to publicly accessible media without creating assets first.
Real-Human Asset Sessions
Some video generation use cases require a verified real-human asset (e.g. a consented portrait for likeness generation). RouterHub exposes a session-based H5 flow: you create a session, receive an H5 link, the subject completes verification on their device, and RouterHub redirects them back to your callback_url with the outcome.
Seedance-Only Workflow: Real-human asset verification sessions are exclusively required by BytePlus Seedance for verified human likeness assets. Other models (MiniMax H3, Runway Gen 4.5, etc.) do not require verification sessions — you can directly pass public URLs or data URIs of real-human images in content (image_url) to generate videos.
Create Session
Create a real-human verification session. The response returns an h5_link that you should open on the subject's device. After the subject completes (or abandons) the flow, BytePlus redirects to the platform callback at /v1/video/assets/sessions/callback, which in turn redirects to your callback_url.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| callback_url | string |
Yes | HTTPS URL on your server that the subject will be redirected to after the H5 flow completes. Must be a valid https:// URL. |
| lang | string |
No | Language for the H5 page. Accepted values: "zh", "en", "zh-Hant". If omitted, the upstream default is used. |
Example Request
curl https://api.routerhub.ai/v1/video/assets/sessions \
-H "Authorization: Bearer $ROUTERHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_url": "https://myapp.example.com/webhooks/real-human-done",
"lang": "en"
}'Response (200 OK)
{
"object": "video.asset_session",
"status": "pending",
"h5_link": "https://visual-validate.byteplusapi.com/h5/verify?token=...",
"expires_at": "2026-08-04T11:00:00Z"
}Session Status Lifecycle
| Status | Description |
|---|---|
| pending | Session created, subject has not yet completed the H5 flow |
| verified | Subject completed verification successfully; an asset group is registered for the organization |
| failed | Verification failed or was abandoned |
Platform Callback (Inbound)
This endpoint is the platform inbound callback that BytePlus redirects the subject's browser to after the H5 flow. It is not intended for direct client use. RouterHub updates the session status and then redirects the subject's browser (HTTP 302) to your callback_url with forwarded query parameters (e.g. group_id, result, code).
Use the forwarded query parameters to correlate the outcome with the session on your side.
Rate Limits & Billing
- Rate limits are enforced during create.
- Prepaid credit is reserved during create and settled when the task completes.
- Timeouts: if a task does not complete within its effective timeout (default 2 hours, overridable via
execution_expires_after), it is marked failed witherror.code = "video_timeout".
Seedance 2.0 billing: Pricing is keyed by resolution tier (720p / 1080p / 4k) and whether the request includes a video reference.