GPT-6 Sol & Claude Opus 5.5 are liveGPT-6 Sol at half the price of 5.6 Sol

Seedance 2.0 API: Text-to-Video and Image-to-Video in Python

One script creates the task, polls it and saves the MP4. Here are the fields, the limits, and two 5-second clips we made on October 30, 2026.

Kiki, OmniaKey COO11 min read
On this page

Seedance 2.0 runs on OmniaKey's media API under the model id dreamina-seedance-2-0-260128. You POST a task to https://api.omniakey.com/v1/media/tasks, poll the task id, then download the MP4. Our two 5-second 720p clips, one from text and one from a still image, took 159 and 139 seconds on October 30, 2026.

Short answer

  • Model id dreamina-seedance-2-0-260128, with three operations: text_to_video, image_to_video and reference_to_video (OmniaKey's video API docs, checked October 30, 2026).
  • Create, poll, download: the create call answers 202 with an id, then you GET /v1/media/tasks/{id} every few seconds until the status is succeeded (OmniaKey's media API overview, checked October 30, 2026).
  • Duration runs from 4 to 15 whole seconds, and the full 2.0 id renders 480P, 720P and 1080P.
  • The whole request body stays under 1 MiB, inline base64 included.
  • Our two 5-second 720p renders finished in 159 seconds (text to video) and 139 seconds (first frame).

What do you need before the first call?

An OmniaKey API key, Python 3 and the exact model id. The script imports only the standard library, so it runs with no virtualenv and no package install.

Export the key as OMNIAKEY_API_KEY before the first call. The script reads it once and sends it as a Bearer token on every request, so the key stays out of the file and out of your git history.

Create with POST https://api.omniakey.com/v1/media/tasks and poll with GET https://api.omniakey.com/v1/media/tasks/{id}. Those two plus POST /v1/media/tasks/{id}/cancel are the whole media API (media API overview, checked October 30, 2026).

Type model ids in full. OmniaKey does not serve the short aliases seedance-2 and seedance-2-5, and they answer 503 no_available_channel (video API docs, checked October 30, 2026).

Video jobs are pre-charged per second of generated output, and the charge comes back automatically when a job ends failed or canceled (media API overview, checked October 30, 2026). For what each second costs now, see current rates on the Seedance 2.0 model page.

Which fields does a Seedance 2.0 request take?

Four top-level fields carry the job, and a parameters object carries the output settings. Only duration has no sensible default, so always send it.

  • model: the full model id (short aliases fail)
  • type: video (the media kind)
  • operation: text_to_video, image_to_video, reference_to_video (bare name, no prefix)
  • input.prompt: one string (scene, action and camera, up to 12,000 characters)
  • input.media[]: {"type": "first_frame", "url": …} (a first frame, or reference_image)

Inside parameters:

  • duration: 4 to 15, whole seconds (decides the price)
  • resolution: 480P, 720P, 1080P (Fast and Mini stop at 720P)
  • ratio: 16:9, 9:16, 1:1, 4:3, 3:4, 21:9, adaptive (adaptive follows the frame)
  • generate_audio: true by default, or false (false gives a silent MP4)
  • n: 1 (must be 1)

Both lists follow OmniaKey's video API docs and media API overview (checked October 30, 2026), with the prompt limit from the model page.

Send the bare operation name; OmniaKey adds the media-type prefix itself (video API docs, checked October 30, 2026). Parameters it does not recognize go to the provider as-is (media API overview, checked October 30, 2026).

One trap catches people who start from the vendor docs: BytePlus's own create-task API puts fields inside content[] and at the top level, so code from a BytePlus tutorial will not run unchanged against /v1/media/tasks (BytePlus's create-task API, checked October 30, 2026).

The Python script

One file runs both operations, and the only difference is the body it builds. Save it as seedance_video.py and run python3 seedance_video.py.

python
import base64
import json
import os
import time
import urllib.error
import urllib.request
import uuid

API = "https://api.omniakey.com/v1/media/tasks"
KEY = os.environ["OMNIAKEY_API_KEY"]
MODEL = "dreamina-seedance-2-0-260128"

def request(method, url, body=None, extra_headers=None):
    headers = {"Authorization": f"Bearer {KEY}", "User-Agent": "omniakey-example/1"}
    data = None
    if body is not None:
        headers["Content-Type"] = "application/json"
        data = json.dumps(body).encode()
    headers.update(extra_headers or {})
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    with urllib.request.urlopen(req, timeout=120) as resp:
        return json.load(resp)

def poll(url, attempts=5):
    for attempt in range(attempts):
        try:
            return request("GET", url)
        except urllib.error.HTTPError as err:
            if err.code < 500 or attempt == attempts - 1:
                raise
        except urllib.error.URLError:
            if attempt == attempts - 1:
                raise
        time.sleep(5 * (attempt + 1))

def run_task(body, out_path):
    task = request("POST", API, body, {"Idempotency-Key": str(uuid.uuid4())})["id"]
    print("task", task, flush=True)
    for _ in range(180):  # 5-second polls, up to 15 minutes
        time.sleep(5)
        state = poll(f"{API}/{task}")
        if state["status"] not in ("queued", "processing"):
            break
    else:
        raise TimeoutError(f"{task} is still {state['status']}: poll it again later, do not resubmit")
    if state["status"] != "succeeded":
        raise RuntimeError(state.get("error") or state["status"])
    asset_url = state["assets"][0]["url"]  # signed link: short-lived, so download now
    req = urllib.request.Request(asset_url, headers={"User-Agent": "omniakey-example/1"})
    with urllib.request.urlopen(req, timeout=300) as resp, open(out_path, "wb") as f:
        f.write(resp.read())
    return task

def text_to_video(prompt, out_path, duration=5, resolution="720P", ratio="16:9"):
    body = {
        "model": MODEL,
        "type": "video",
        "operation": "text_to_video",
        "input": {"prompt": prompt},
        "parameters": {"duration": duration, "resolution": resolution, "ratio": ratio},
    }
    return run_task(body, out_path)

def image_to_video(prompt, image_path, out_path, duration=5, resolution="720P", audio=False):
    with open(image_path, "rb") as f:
        raw = f.read()
    if len(raw) > 700_000:  # base64 adds a third, and the whole body must stay under 1 MiB
        raise ValueError("first frame too large: save it as a JPEG about 1024 px wide")
    frame = "data:image/jpeg;base64," + base64.b64encode(raw).decode()
    body = {
        "model": MODEL,
        "type": "video",
        "operation": "image_to_video",
        "input": {"prompt": prompt, "media": [{"type": "first_frame", "url": frame}]},
        "parameters": {"duration": duration, "resolution": resolution, "ratio": "adaptive",
                       "generate_audio": audio},
    }
    return run_task(body, out_path)

if __name__ == "__main__":
    print("done", text_to_video(
        "Close-up of a tall glass on a wooden bar counter. Sparkling water is poured over ice; bubbles "
        "rise and the ice cubes clink and shift. Warm evening light from the left. The camera slowly "
        "pushes in. Sound of pouring, fizzing and clinking ice. No people, no text.",
        "sparkling-glass.mp4",
    ))
    print("done", image_to_video(
        "The cat slowly wakes, stretches its front paws and yawns, then looks toward the window. "
        "Raindrops keep running down the glass. The camera stays still.",
        "cat.jpg",
        "cat-wakes.mp4",
    ))

Six parts of this file matter before you change anything.

  • Every create request carries a random Idempotency-Key; replaying that key with the same body returns the original task with HTTP 200 and never charges twice (media API overview, checked October 30, 2026).
  • The loop sleeps 5 seconds between polls and stops after 180 tries, which is 15 minutes.
  • A timeout means the job is still rendering. Poll the same task id again later and never resubmit, or you pay for a second clip.
  • assets[0].url is a signed link that lasts about an hour, so the script downloads as soon as the task succeeds (media API overview, checked October 30, 2026).
  • progress.percent can sit at 0 while the job runs, so the loop follows status and nothing else (video API docs, checked October 30, 2026).
  • The image function refuses a file over 700,000 bytes, because base64 adds a third and the body has to stay under 1 MiB.

The request shape matches Kling 3.0 on the same API, so switching models changes the model id and the parameters, not the code around them.

How do you send a first frame for image to video?

Put the picture in input.media[] with "type": "first_frame", inline as a data:image/jpeg;base64,... URI, the shape our cat clip used.

input.media[] accepts a public HTTPS URL or an inline data URI, and the whole body is capped at 1 MiB (video API docs, checked October 30, 2026). OmniaKey's own test saw an ordinary HTTPS frame link rejected with provider_submit_rejected, which is why the example sends a data URI (video API docs, checked October 30, 2026).

Base64 adds a third, so start from a JPEG about 1024 pixels wide: our first frame was 134,356 bytes, and a 4 MB PNG would bounce.

Keep parameters.ratio at adaptive here: it follows the shape of the picture you sent, which is how a 1024×571 still became a 1280×720 clip. For reference_to_video, the same array takes "type": "reference_image" (video API docs, checked October 30, 2026).

Two clips we made

Both tasks went out on October 30, 2026 with the file above, unchanged. The first is text to video, task task_uF2QUtbeAAUW11lXEaLxNw1ZnBUoqvNk, created at 01:39:43 UTC and succeeded at 01:42:22 UTC. The prompt asked for the sound of the pour, so audio stayed on.

prompt
Close-up of a tall glass on a wooden bar counter. Sparkling water is poured over ice; bubbles rise and the ice cubes clink and shift. Warm evening light from the left. The camera slowly pushes in. Sound of pouring, fizzing and clinking ice. No people, no text.
Eight frames of a tall glass of sparkling water on a wooden bar, from the empty pour to a full glass with bubbles near the bottom.
Eight frames from the glass clip in time order, 0.0 to 5.0 seconds. Seedance 2.0 via the OmniaKey API, text to video, 5 seconds, 720P, made October 30, 2026.

The frames show a glass with ice on a wooden bar, bottles and warm lights blurred behind it. The glass grows from the first frame to the last, the push-in the prompt asked for, and ends full with bubbles near the bottom.

The second is image to video, task task_u85W0jn1sQLkrszJlxqXF1hxJYhsndxK, created at 01:42:33 UTC and succeeded at 01:44:52 UTC. The first frame is a still of a ginger cat asleep on a stack of old books by a rainy window (1024×571 pixels), sent as a data URI with "type": "first_frame".

prompt
The cat slowly wakes, stretches its front paws and yawns, then looks toward the window. Raindrops keep running down the glass. The camera stays still.
Eight frames of a ginger cat asleep on a stack of books by a rainy window, waking, stretching its paws and yawning.
Eight frames from the cat clip in time order, 0.0 to 5.0 seconds. Seedance 2.0 via the OmniaKey API, image to video, 5 seconds, 720P, made October 30, 2026.

The cat lifts its head, opens its eyes, stretches both front paws over the edge of the books, yawns wide and turns its head toward the window, in the order the prompt gave. The rain keeps running down the glass and the framing holds still, as asked.

The ginger cat still sent as the first frame on the left, and the last frame of the clip on the right, with its paws stretched over the books and its head turned to the window.
Left: the still sent as the first frame. Right: the last frame of the cat clip. Seedance 2.0 via the OmniaKey API, image to video, 5 seconds, 720P, made October 30, 2026.

These two runs cover text to video and a first frame at 720P for 5 seconds. We did not test 1080P, Fast, Mini, 2.5, reference_to_video, an HTTPS frame link, or any other duration.

What the files measured

Both downloads are H.264 MP4 at 1280×720 and 24 fps: 121 frames, 5.04 seconds.

ClipGlassCat
Operationtext to videoimage to video
Time to finish159 s139 s
File size2,070,248 bytes1,352,027 bytes
Video1280×720, 24 fps, 121 frames1280×720, 24 fps, 121 frames
AudioAAC tracknone

The glass clip's track carries pouring, fizzing and clinking ice, with no voices and no music. The cat clip has no track at all, because the request sent generate_audio: false.

Both task records reported usage.completion_tokens: 108900, which is 121 × 1280 × 720 ÷ 1024, the token formula BytePlus uses to estimate video billing (BytePlus's pricing page, checked October 30, 2026).

OmniaKey's docs say a 6-second 720p clip usually finishes within about a minute (video API docs, checked October 30, 2026). Ours took 159 and 139 seconds, and three 5-second 720p clips in our October 26 comparison took 144.8, 319.0 and 184.7 seconds. Plan for 2 to 5 minutes and poll rather than hold a request open.

Which errors and limits will you hit?

Check three settings first: the model id, a duration from 4 to 15, and audio, which the provider's filter can reject.

Status / CodeWhat to change
400 invalid_requestbody or input.prompt
400 model_price_errorduration to 4–15 s
403 model_not_allowedkey's model access
409 idempotency_key_reuseda new key per body
503 no_available_channelfull id, bare operation
failed task, provider errorread error, then retry

Those codes come from the media API overview and the video API docs (checked October 30, 2026). A task that ends failed refunds its pre-charge, so a rejected render costs you nothing but time.

Watch the audio setting. Seedance renders a track by default, and leaving it on made the provider reject a 4-second 480P request with OutputAudioSensitiveContentDetected.PolicyViolation, so send parameters.generate_audio: false unless the scene needs sound (video API docs, checked October 30, 2026).

BytePlus says the 2.0 series does not accept reference images or videos that contain real human faces (BytePlus's Seedance 2.0 page, checked October 30, 2026). Seedance 2.0 also has no seed and no camera_fixed, though both are fields on the vendor's create-task API (BytePlus's create-task API, checked October 30, 2026).

Fifteen seconds is the ceiling on this family, and duration: -1 buys nothing more; a 20- or 30-second take belongs on Seedance 2.5 (the Seedance 2.0 model page, checked October 30, 2026).

OmniaKey publishes no fixed rate limits; treat HTTP 429 as retryable with exponential backoff (OmniaKey's API errors page, checked October 30, 2026).

Seedance 2.0, Fast, Mini or 2.5?

Four tiers take the same request body, so switching costs one model id and, on Fast and Mini, a lower resolution ceiling.

  • Seedance 2.0, dreamina-seedance-2-0-260128: 480P, 720P and 1080P, 4 to 15 seconds. BytePlus's list price for a 5-second clip is $0.76 at 720p and $0.35 at 480p (BytePlus's pricing page, checked October 30, 2026).
  • Seedance 2.0 Fast, dreamina-seedance-2-0-fast-260128: 480P and 720P, 4 to 15 seconds. BytePlus's list price for a 5-second clip is $0.60 at 720p and $0.28 at 480p.
  • Seedance 2.0 Mini, dreamina-seedance-2-0-mini-260615: 480P and 720P, 4 to 15 seconds. BytePlus's list price for a 5-second clip is $0.38 at 720p and $0.18 at 480p.
  • Seedance 2.5, dreamina-seedance-2-5-260628: the only id in the family that takes a 20- or 30-second take, and the slowest: about four minutes for a 4-second 480P clip (video API docs, checked October 30, 2026). Our Seedance 2.5 review covers the picture side.

OmniaKey's model pages report a 4-second 480P clip at about a minute on 2.0, two minutes on Fast and 100 seconds on Mini (checked October 30, 2026).

Take the full 2.0 id when you need a 1080P master, because Fast and Mini stop at 720P (video API docs, checked October 30, 2026). OmniaKey sets its own rates for these ids, so see current rates on the Seedance 2.0 model page.

FAQ

Is there an official Seedance 2.0 API?

Yes. BytePlus ModelArk sells it, and activating it there needs an account balance above USD 30, a Savings Plan or a Seedance resource pack (BytePlus's Seedance 2.0 page, checked October 30, 2026). The script above calls the same model id through OmniaKey's media API instead.

Can I send an image URL instead of base64?

OmniaKey's docs list a public HTTPS URL next to the inline data URI (video API docs, checked October 30, 2026). Our runs went inline because an HTTPS frame link came back as provider_submit_rejected in OmniaKey's own test, so try a URL on a test clip first.

How do I turn off the sound?

Send parameters.generate_audio: false, and the MP4 arrives with no audio track at all, as the cat clip did. Audio is on by default on this model.

Why did my request time out?

The clip is usually still rendering, since 5-second 720p jobs took 139 to 319 seconds in our runs. Poll the same task id again, never resubmit, and raise the 15-minute loop limit for 15-second clips.

The signed link in assets[0].url lasts about an hour, and the task record itself is kept for 24 hours after creation (media API overview, checked October 30, 2026). Download on the first success, or poll the task again for a fresh link.

Sources checked

OmniaKey's own documentation, checked October 30, 2026: video API, media API overview and API errors.

Models in this article

Seedance 2.0AI videovideo APIimage-to-videoPython

Related articles

Recent posts