Video Generation

RouterHub provides an asynchronous video generation API powered by Seedance 2.0 (BytePlus ModelArk). 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.

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

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

POST /v1/video/generations

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".
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. Must be between 4 and 15 seconds (inclusive). If omitted, the provider default (5s) is used.
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).
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": "..."}.
video_url object Video reference (required when type is "video_url"). Contains {"url": "..."} or {"asset_id": "..."}.
audio_url object Audio reference (required when type is "audio_url"). Contains {"url": "..."} or {"asset_id": "..."}.

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.

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}
  }'

Get Video Generation

GET /v1/video/generations/{task_id}

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: For succeeded tasks, content.video_url is a pre-signed download URL that is time-limited (usually 24 hours). Download promptly or call Get again for a fresh URL. 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

GET /v1/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 /v1/video/generations/{task_id}

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}), 200
const 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.

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

POST /v1/video/assets

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

GET /v1/video/assets/{asset_id}

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

GET /v1/video/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.

POST /v1/video/assets/sessions

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
GET /v1/video/assets/sessions/callback

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

Seedance 2.0 billing: Pricing is keyed by resolution tier (720p / 1080p / 4k) and whether the request includes a video reference.