> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ruapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Video generation: Seedance 2.0

> Seedance 2.0: text / image / first-last frame / reference-video to video, with built-in synced audio — OpenAI-compatible async API, curl & Python examples

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.

<Tip>
  **Audio is on by default** — Seedance 2.0 videos come with a synced audio track (dialogue / ambient / SFX), no extra parameters needed.
</Tip>

<Warning>
  **Generated videos are kept for 7 days only.** Download and save them right after generation; the link stops working once it expires.
</Warning>

## Available models

| Model ID                   | Description                            |
| -------------------------- | -------------------------------------- |
| `doubao-seedance-2.0-fast` | Faster and cheaper, the default choice |
| `doubao-seedance-2.0`      | Full version with higher quality       |

For exact prices, see the **[pricing page](https://www.ruapi.ai/pricing)** (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:

<Steps>
  <Step title="Submit a task">
    `POST /v1/videos` → returns a task `id`
  </Step>

  <Step title="Poll the status">
    `GET /v1/videos/{id}` → until `status` becomes `completed`
  </Step>

  <Step title="Download the video">
    `GET /v1/videos/{id}/content` → get the MP4
  </Step>
</Steps>

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

<CodeGroup>
  ```bash curl theme={null}
  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" }
    }'
  ```

  ```python Python (requests) theme={null}
  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)
  ```

  ```javascript Node.js (fetch) theme={null}
  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);
  ```
</CodeGroup>

Response (`id` is the task number, used in the next two steps):

```json theme={null}
{
  "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`.

<CodeGroup>
  ```bash curl theme={null}
  curl https://www.ruapi.ai/v1/videos/task_YOUR_TASK_ID \
    -H "Authorization: Bearer sk-YOUR_KEY"
  ```

  ```python Python (requests) theme={null}
  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"))
  ```
</CodeGroup>

Once complete (`metadata.url` is the download link):

```json theme={null}
{
  "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

<CodeGroup>
  ```bash curl theme={null}
  curl -L https://www.ruapi.ai/v1/videos/task_YOUR_TASK_ID/content \
    -H "Authorization: Bearer sk-YOUR_KEY" \
    -o seedance.mp4
  ```

  ```python Python (requests) theme={null}
  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")
  ```
</CodeGroup>

## Full example: the whole flow in one script

Copy-paste and run, replacing `sk-YOUR_KEY` with your own key:

```python Python theme={null}
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:

| Field      | Type   | Description                                              |
| ---------- | ------ | -------------------------------------------------------- |
| `model`    | string | `doubao-seedance-2.0-fast` or `doubao-seedance-2.0`      |
| `prompt`   | string | **Required.** Text description of the scene              |
| `seconds`  | string | Duration (sec), **4–15**, e.g. `"5"`, `"10"`             |
| `metadata` | object | Resolution, aspect ratio, image/video inputs — see below |

Fields inside `metadata`:

| Field        | Type      | Description                                                                                   |
| ------------ | --------- | --------------------------------------------------------------------------------------------- |
| `resolution` | string    | `"480p"` / `"720p"` / `"1080p"`                                                               |
| `ratio`      | string    | Aspect ratio, e.g. `"16:9"`, `"9:16"`, `"1:1"`                                                |
| `input_type` | string    | Input type (usually omit — auto-detected): `text_to_video` / `first_last_frame` / `reference` |
| `images`     | string\[] | Array of image URLs (for image-to-video)                                                      |
| `videos`     | string\[] | Array of video URLs (for reference-video)                                                     |

<Note>
  Higher resolution and longer duration use more tokens and cost more. If unsure, start with `480p` / `5` seconds, then tune.
</Note>

## 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`.

| Mode             | How to pass input                | Inferred `input_type` |
| ---------------- | -------------------------------- | --------------------- |
| Text-to-video    | `prompt` only                    | `text_to_video`       |
| Image-to-video   | `metadata.images` — **1** image  | `reference`           |
| First-last frame | `metadata.images` — **2** images | `first_last_frame`    |
| Reference-video  | `metadata.videos` — **1** video  | `reference`           |

<Note>
  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).
</Note>

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.

<CodeGroup>
  ```bash curl theme={null}
  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" }
    }'
  ```

  ```python Python theme={null}
  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
  ```
</CodeGroup>

### 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`.

<CodeGroup>
  ```bash curl theme={null}
  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"]
      }
    }'
  ```

  ```python Python theme={null}
  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"])
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```bash curl theme={null}
  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"
        ]
      }
    }'
  ```

  ```python Python theme={null}
  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"])
  ```
</CodeGroup>

### 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).

<CodeGroup>
  ```bash curl theme={null}
  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"]
      }
    }'
  ```

  ```python Python theme={null}
  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"])
  ```
</CodeGroup>

<Warning>
  **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`).
</Warning>

### 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**).

<CodeGroup>
  ```bash curl theme={null}
  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"
        ]
      }
    }'
  ```

  ```python Python theme={null}
  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"])
  ```
</CodeGroup>

<Note>
  Same for videos: replace `images` with `videos` and pass several short clips (each ideally ≤ 7 seconds with a clear subject, ≤ 15 seconds total).
</Note>

## Task statuses

| `status`      | Meaning                                   |
| ------------- | ----------------------------------------- |
| `queued`      | Queued                                    |
| `in_progress` | Generating                                |
| `completed`   | Done, `metadata.url` is the download link |
| `failed`      | Failed, 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](https://www.ruapi.ai/pricing)**.

## FAQ

<AccordionGroup>
  <Accordion title="model_not_found / 404 error">
    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`).
  </Accordion>

  <Accordion title="Stuck on queued / in_progress">
    That's normal. Generation usually takes **1-3 minutes**; keep polling and don't set the interval too short.
  </Accordion>

  <Accordion title="Downloaded file is empty / won't open">
    It's likely been **more than 7 days** and the upstream link expired. Download right after generation.
  </Accordion>

  <Accordion title="Reference-video task failed">
    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.
  </Accordion>

  <Accordion title="Which address do I poll?">
    Use the **plural** `GET /v1/videos/{id}`. It's the OpenAI-compatible format, and `metadata.url` in the response is directly the download link.
  </Accordion>
</AccordionGroup>

## Next

* [Pricing & billing rules](/en/pricing)
* Questions? [support@ruapi.ai](mailto:support@ruapi.ai)
