Generate a video with Seedance 2.0 Fast in Python
A complete script for doubao-seedance-2-0-fast-260128: submit a generation to /v1/video/generations, poll the task until it is ready, save the result URL and download the file. Then a few notes on writing prompts for Seedance and on cost.
Before you run it
Account, balance, key
Sign up at fishtoken.ai/register, add balance on the top-up page (FishToken is prepaid; the balance does not expire), and create a key under API keys.
Install one package
pip install requests. The OpenAI SDK is not needed here because video generation has its own endpoint.Export the key
export FISHTOKEN_API_KEY=your-key-here. The script reads it from the environment and never has it in the source.
The script
Save as seedance_fast.py. It has three steps: submit() posts the request, wait_for() polls the task endpoint, video_url() pulls the URL out of the finished task.
"""Generate a video with Seedance 2.0 Fast through FishToken, wait for it, save the result URL.
pip install requests
export FISHTOKEN_API_KEY=your-key-here
python3 seedance_fast.py
"""
import os
import sys
import time
import requests
API = "https://fishtoken.ai/v1"
MODEL = "doubao-seedance-2-0-fast-260128"
HEADERS = {"Authorization": f"Bearer {os.environ['FISHTOKEN_API_KEY']}"}
PROMPT = ("Vertical shot: a woven rattan handbag turning slowly on a sunlit wooden table, "
"soft shadows, shallow depth of field")
POLL_EVERY = 5 # seconds between status checks
MAX_WAIT = 10 * 60 # give up after ten minutes
def submit(prompt: str) -> str:
"""Submit a generation task and return its task ID."""
r = requests.post(f"{API}/video/generations", headers=HEADERS,
json={"model": MODEL, "prompt": prompt}, timeout=60)
r.raise_for_status()
data = r.json()
# PLACEHOLDER: the field that holds the task ID is defined in https://fishtoken.ai/docs
# (commonly "id" or "task_id"). Print data once and adjust the line below if needed.
task_id = data.get("id") or data.get("task_id")
if not task_id:
sys.exit(f"no task ID in response: {data}")
return task_id
def wait_for(task_id: str) -> dict:
"""Poll the task until it finishes or MAX_WAIT passes."""
deadline = time.time() + MAX_WAIT
while time.time() < deadline:
r = requests.get(f"{API}/video/generations/{task_id}", headers=HEADERS, timeout=60)
r.raise_for_status()
task = r.json()
# PLACEHOLDER: check the task endpoint in https://fishtoken.ai/docs for the exact
# status values and the field that carries the video URL, then adjust these two lines.
status = str(task.get("status", "")).lower()
if status in ("succeeded", "completed", "success", "done"):
return task
if status in ("failed", "error", "cancelled"):
sys.exit(f"task {task_id} failed: {task}")
print(f" {status or 'pending'} ... checking again in {POLL_EVERY}s")
time.sleep(POLL_EVERY)
sys.exit(f"task {task_id} not finished after {MAX_WAIT}s")
def video_url(task: dict) -> str:
"""Find the first http(s) URL in the finished task, whatever key it sits under."""
stack = [task]
while stack:
item = stack.pop()
if isinstance(item, dict):
stack.extend(item.values())
elif isinstance(item, list):
stack.extend(item)
elif isinstance(item, str) and item.startswith("http"):
return item
sys.exit(f"no URL in finished task: {task}")
if __name__ == "__main__":
task_id = submit(PROMPT)
print(f"submitted task {task_id}")
finished = wait_for(task_id)
url = video_url(finished)
with open("seedance_result.txt", "a", encoding="utf8") as f:
f.write(f"{task_id}\t{url}\n")
print(f"video ready: {url}")
print("saved to seedance_result.txt")
# Optional: download the file. Result URLs may expire, so do this soon after the task finishes.
mp4 = requests.get(url, timeout=300)
mp4.raise_for_status()
with open(f"{task_id}.mp4", "wb") as f:
f.write(mp4.content)
print(f"downloaded {task_id}.mp4 ({len(mp4.content) // 1024} KB)")Two placeholders. The exact names of the task-ID field, the status values and the URL field are defined by the FishToken docs. The script guesses the common names and otherwise prints the raw response so you can see what to adjust; the comments marked PLACEHOLDER show where. Other fields Seedance accepts, such as duration or size, go next to prompt in submit().
$ python3 seedance_fast.py
submitted task TASK_ID
pending ... checking again in 5s
pending ... checking again in 5s
video ready: https://.../TASK_ID.mp4
saved to seedance_result.txt
downloaded TASK_ID.mp4 (... KB)Writing prompts for Seedance
One shot per clip
Describe a single scene with one subject and one camera move. Cuts and scene changes inside one prompt tend to produce muddled results; generate two clips and edit them together instead.
Name the subject, the motion, the light
“A rattan handbag turning slowly on a wooden table, sunlight from the left, soft shadows” gives the model something concrete. Adjectives alone (“beautiful”, “cinematic”) do little.
Say the orientation
Start the prompt with “Vertical shot” for short-video and social formats, or “Wide shot” for landscape, and check the size options in the docs if the model takes an explicit size field.
Draft on Fast, then rerun
Iterate on wording with doubao-seedance-2-0-fast-260128. When a prompt works, run the same text on doubao-seedance-2-0-260128 or doubao-seedance-2-5-260628 by changing MODEL; the key and balance are the same.
Keep it within the rules
No real person's likeness, no brand logos you do not own, nothing sexual or violent. Requests like that may be refused by the upstream model service and are against the content rules either way.
Log what you tried
The script appends every task ID and URL to seedance_result.txt. Add the prompt to that line too, and you have a record of which wording produced which clip.
What it costs
Each generation is charged to your prepaid balance when the task runs; polling the task endpoint is not a generation. Video models are billed per request or by usage depending on the model, and the current price for each Seedance ID is on fishtoken.ai/pricing. Check it before a batch job, and remember that prices on that page are shown in platform credits.
Rate limits
60 requests per minute for standard accounts and 120 for accounts that have topped up. The poll interval of five seconds in the script stays well inside that even with several tasks running; if you parallelise, add a back-off on 429 responses.
Before you publish
Content filtering depends on the upstream model service. You are responsible for what you generate and publish: no sexual content, no graphic violence, and no real person's likeness or voice without their consent. Label AI-generated content where the platform you publish on or local law requires it.
Generate your first clip
Create an account, add balance, create a key and run the script.