Skip to main content
Seedance 2.0 (ByteDance) is a multimodal video generation model. It supports four input types — text-to-video, image-to-video, first-last frame, and reference-video — and the output includes synced audio by default. RuAPI exposes an OpenAI-compatible async video API: submit a task → poll status → download the video. Billed in USDT, no foreign card required.
Audio is on by default — Seedance 2.0 videos come with a synced audio track (dialogue / ambient / SFX), no extra parameters needed.
Generated videos are kept for 7 days only. Download and save them right after generation; the link stops working once it expires.

Available models

Model IDDescription
doubao-seedance-2.0-fastFaster and cheaper, the default choice
doubao-seedance-2.0Full version with higher quality
For exact prices, see the pricing page (video is billed per token).

Endpoint & auth

  • Base URL: https://www.ruapi.ai/v1
  • Auth: header Authorization: Bearer sk-YOUR_KEY (create one in the console under “Tokens”)
Generation is async, in three steps:
1

Submit a task

POST /v1/videos → returns a task id
2

Poll the status

GET /v1/videos/{id} → until status becomes completed
3

Download the video

GET /v1/videos/{id}/content → get the MP4

Step 1. Submit a task (text-to-video)

curl https://www.ruapi.ai/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0-fast",
    "prompt": "Night city, neon reflecting on wet streets, cyberpunk style, camera slowly pushing in",
    "seconds": "5",
    "metadata": { "resolution": "720p", "ratio": "16:9" }
  }'
import requests

resp = requests.post(
    "https://www.ruapi.ai/v1/videos",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "Night city, neon reflecting on wet streets, cyberpunk style, camera slowly pushing in",
        "seconds": "5",
        "metadata": {"resolution": "720p", "ratio": "16:9"},
    },
)
task_id = resp.json()["id"]
print("Task submitted:", task_id)
const resp = await fetch("https://www.ruapi.ai/v1/videos", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk-YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "doubao-seedance-2.0-fast",
    prompt: "Night city, neon reflecting on wet streets, cyberpunk style, camera slowly pushing in",
    seconds: "5",
    metadata: { resolution: "720p", ratio: "16:9" },
  }),
});
const { id } = await resp.json();
console.log("Task submitted:", id);
Response (id is the task number, used in the next two steps):
{
  "id": "task_guXmm9BZpLpYaxfky7mkR2kSlRtgDSe9",
  "object": "video",
  "model": "doubao-seedance-2.0-fast",
  "status": "queued",
  "progress": 0,
  "created_at": 1783579686
}

Step 2. Poll the status

Query the GET /v1/videos/{id} route about every 5 seconds. status goes through queued → in_progress → completed.
curl https://www.ruapi.ai/v1/videos/task_YOUR_TASK_ID \
  -H "Authorization: Bearer sk-YOUR_KEY"
import requests

r = requests.get(
    f"https://www.ruapi.ai/v1/videos/{task_id}",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
).json()
print(r["status"], r.get("progress"))
Once complete (metadata.url is the download link):
{
  "id": "task_guXmm9BZpLpYaxfky7mkR2kSlRtgDSe9",
  "object": "video",
  "status": "completed",
  "progress": 100,
  "metadata": {
    "url": "https://www.ruapi.ai/v1/videos/task_guXmm9BZpLpYaxfky7mkR2kSlRtgDSe9/content"
  }
}

Step 3. Download the video

curl -L https://www.ruapi.ai/v1/videos/task_YOUR_TASK_ID/content \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -o seedance.mp4
video = requests.get(
    f"https://www.ruapi.ai/v1/videos/{task_id}/content",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
)
with open("seedance.mp4", "wb") as f:
    f.write(video.content)
print("Saved seedance.mp4")

Full example: the whole flow in one script

Copy-paste and run, replacing sk-YOUR_KEY with your own key:
Python
import time
import requests

BASE = "https://www.ruapi.ai/v1"
HEADERS = {"Authorization": "Bearer sk-YOUR_KEY"}

# 1) Submit a task
resp = requests.post(
    f"{BASE}/videos",
    headers=HEADERS,
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "Night city, neon reflecting on wet streets, cyberpunk style, camera slowly pushing in",
        "seconds": "5",
        "metadata": {"resolution": "720p", "ratio": "16:9"},
    },
)
resp.raise_for_status()
task_id = resp.json()["id"]
print("Task submitted:", task_id)

# 2) Poll until done (usually 1-3 minutes)
while True:
    r = requests.get(f"{BASE}/videos/{task_id}", headers=HEADERS).json()
    print("Status:", r.get("status"), r.get("progress"))
    if r.get("status") == "completed":
        break
    if r.get("status") == "failed":
        raise RuntimeError(f"Generation failed: {r.get('error')}")
    time.sleep(5)

# 3) Download (result is kept 7 days — save it promptly)
video = requests.get(f"{BASE}/videos/{task_id}/content", headers=HEADERS)
with open("seedance.mp4", "wb") as f:
    f.write(video.content)
print("Saved seedance.mp4")

Parameters

Top-level fields:
FieldTypeDescription
modelstringdoubao-seedance-2.0-fast or doubao-seedance-2.0
promptstringRequired. Text description of the scene
secondsstringDuration (sec), 4–15, e.g. "5", "10"
metadataobjectResolution, aspect ratio, image/video inputs — see below
Fields inside metadata:
FieldTypeDescription
resolutionstring"480p" / "720p" / "1080p"
ratiostringAspect ratio, e.g. "16:9", "9:16", "1:1"
input_typestringInput type (usually omit — auto-detected): text_to_video / first_last_frame / reference
imagesstring[]Array of image URLs (for image-to-video)
videosstring[]Array of video URLs (for reference-video)
Higher resolution and longer duration use more tokens and cost more. If unsure, start with 480p / 5 seconds, then tune.

The four input modes

input_type usually doesn’t need to be set — it’s inferred from the number of images/videos; you can also set it explicitly via metadata.input_type.
ModeHow to pass inputInferred input_type
Text-to-videoprompt onlytext_to_video
Image-to-videometadata.images1 imagereference
First-last framemetadata.images2 imagesfirst_last_frame
Reference-videometadata.videos1 videoreference
Images / videos must be publicly reachable URLs (the upstream fetches them); images support JPG / PNG / WebP, videos support MP4. base64 inline is not supported (an oversized request body will fail).
Below, each mode shows only step 1 “Submit”; steps 2 (poll) and 3 (download) are identical for all modes.

Mode 1. Text-to-video

Just a text prompt — generated entirely from the description.
curl https://www.ruapi.ai/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0-fast",
    "prompt": "Night city, neon reflecting on wet streets, cyberpunk style, camera slowly pushing in",
    "seconds": "5",
    "metadata": { "resolution": "720p", "ratio": "16:9" }
  }'
import requests

resp = requests.post(
    "https://www.ruapi.ai/v1/videos",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "Night city, neon reflecting on wet streets, cyberpunk style, camera slowly pushing in",
        "seconds": "5",
        "metadata": {"resolution": "720p", "ratio": "16:9"},
    },
)
print(resp.json()["id"])  # get the task id, then poll & download as in the full script above

Mode 2. Image-to-video (1 image)

Put 1 image URL in metadata.images; it’s used as the basis to animate. Describe the motion / camera in prompt.
curl https://www.ruapi.ai/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0-fast",
    "prompt": "The person in the image smiles and waves naturally, camera slowly pushing in",
    "seconds": "5",
    "metadata": {
      "resolution": "720p",
      "ratio": "16:9",
      "images": ["https://your-domain/portrait.jpg"]
    }
  }'
import requests

resp = requests.post(
    "https://www.ruapi.ai/v1/videos",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "The person in the image smiles and waves naturally, camera slowly pushing in",
        "seconds": "5",
        "metadata": {
            "resolution": "720p",
            "ratio": "16:9",
            "images": ["https://your-domain/portrait.jpg"],
        },
    },
)
print(resp.json()["id"])

Mode 3. First-last frame (2 images)

Put 2 images in metadata.images: the 1st = start frame, the 2nd = end frame. It generates a smooth transition from the first to the second.
curl https://www.ruapi.ai/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0-fast",
    "prompt": "Smooth transition from the first frame to the second, natural camera motion, cinematic",
    "seconds": "5",
    "metadata": {
      "resolution": "720p",
      "ratio": "16:9",
      "images": [
        "https://your-domain/first.jpg",
        "https://your-domain/last.jpg"
      ]
    }
  }'
import requests

resp = requests.post(
    "https://www.ruapi.ai/v1/videos",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "Smooth transition from the first frame to the second, natural camera motion, cinematic",
        "seconds": "5",
        "metadata": {
            "resolution": "720p",
            "ratio": "16:9",
            "images": [
                "https://your-domain/first.jpg",
                "https://your-domain/last.jpg",
            ],
        },
    },
)
print(resp.json()["id"])

Mode 4. Reference-video (1 video)

Put 1 short video URL in metadata.videos; the model reinterprets it while keeping the subject’s motion (e.g. restyle or change the scene).
curl https://www.ruapi.ai/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0-fast",
    "prompt": "Keep the person's motion from the video, restyle to a neon cyberpunk street",
    "seconds": "5",
    "metadata": {
      "resolution": "480p",
      "ratio": "9:16",
      "videos": ["https://your-domain/dance.mp4"]
    }
  }'
import requests

resp = requests.post(
    "https://www.ruapi.ai/v1/videos",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "Keep the person's motion from the video, restyle to a neon cyberpunk street",
        "seconds": "5",
        "metadata": {
            "resolution": "480p",
            "ratio": "9:16",
            "videos": ["https://your-domain/dance.mp4"],
        },
    },
)
print(resp.json()["id"])
Reference-video is picky about input: use a short clip that is ≤ 15 seconds with a clear subject (e.g. a person). Cartoons, clips without an obvious subject, or overly long videos often fail. It’s best if the reference’s aspect ratio matches your target ratio (e.g. a vertical source → 9:16).

Advanced: multiple images / videos

images and videos are arrays — you can pass several references at once (both verified):
  • Multiple images: put 3 or more images in images (officially up to 9). The model combines subject / scene / style across them. With 3+ images, input_type is auto-detected as reference.
  • Multiple videos: put 2 or more clips in videos (officially up to 3, total duration ≤ 15 seconds).
curl https://www.ruapi.ai/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0-fast",
    "prompt": "Combine the subject and style from these images into one coherent video, natural camera motion",
    "seconds": "5",
    "metadata": {
      "resolution": "720p",
      "ratio": "16:9",
      "images": [
        "https://your-domain/ref1.jpg",
        "https://your-domain/ref2.jpg",
        "https://your-domain/ref3.jpg"
      ]
    }
  }'
import requests

resp = requests.post(
    "https://www.ruapi.ai/v1/videos",
    headers={"Authorization": "Bearer sk-YOUR_KEY"},
    json={
        "model": "doubao-seedance-2.0-fast",
        "prompt": "Combine the subject and style from these images into one coherent video, natural camera motion",
        "seconds": "5",
        "metadata": {
            "resolution": "720p",
            "ratio": "16:9",
            "images": [
                "https://your-domain/ref1.jpg",
                "https://your-domain/ref2.jpg",
                "https://your-domain/ref3.jpg",
            ],
        },
    },
)
print(resp.json()["id"])
Same for videos: replace images with videos and pass several short clips (each ideally ≤ 7 seconds with a clear subject, ≤ 15 seconds total).

Task statuses

statusMeaning
queuedQueued
in_progressGenerating
completedDone, metadata.url is the download link
failedFailed, reason in the error field

Billing

Video is billed per token (resolution × duration). A pre-charge is held on submit, then recalculated by actual usage on completion — the difference is refunded. The final charge is what actually got deducted. See the pricing page.

FAQ

Check two things: is the model name correct (doubao-seedance-2.0-fast), and is the address https://www.ruapi.ai/v1/videos (with /v1).
That’s normal. Generation usually takes 1-3 minutes; keep polling and don’t set the interval too short.
It’s likely been more than 7 days and the upstream link expired. Download right after generation.
This mode is picky about input. Make sure the reference video is ≤ 15 seconds, has a clear subject, and is a publicly reachable MP4. Cartoons or clips without an obvious subject often fail.
Use the plural GET /v1/videos/{id}. It’s the OpenAI-compatible format, and metadata.url in the response is directly the download link.

Next