Seedance 2.5 in Python - Video via API, Step by Step
Call the Seedance 2.5 video API from Python: per-second pricing, every parameter, text-to-video and image-to-video code, polling, webhooks and errors.
The Seedance 2.5 API turns a text prompt (and optionally a first-frame image) into a video clip with generated audio, billed per second of output. On Jantaris you call it with POST /v1/generate and "model": "seedance-2.5", get a task back immediately, then collect the MP4 by polling or webhook. The current rate is from $0.261 per second, and the exact cost of each request is returned before it runs.
Below is everything I needed when I wired Seedance 2.5 into my own pipeline: what the model can do according to ByteDance, which parameters matter, and Python code for the common cases.
What Seedance 2.5 is
Seedance 2.5 is ByteDance Seed's video generation model, officially released on July 31, 2026, with the developer API opening in early August. The points that matter for API users, based on ByteDance's announcement and API documentation:
- Long single takes. Up to 30 seconds of video in one generation. The vendor's API docs list 4 to 30 seconds.
- Audio in the same pass. Sound is generated together with the picture, not added afterwards.
- Heavy referencing. The model itself accepts up to 30 images, 10 video clips and 10 audio clips as references in one request.
- Resolution. The vendor's API documentation lists 480p and 720p output. Some sites advertise 1080p or 4K for Seedance 2.5, but I could not find that in ByteDance's own docs, so I do not rely on it.
For my work the 30-second limit is the headline. Veo 3.1 stops at 8 seconds per generation and Kling 3.0 at 15, which means stitching for anything longer. With Seedance 2.5 a full short-form scene fits in one call.
Seedance 2.5 API pricing
Seedance 2.5 is billed per second of generated video. The current Jantaris rate is from $0.261 per second. A clip's cost is the rate multiplied by duration.
You never have to do that math blind. Every POST /v1/generate response includes estimated_cost, calculated before the job starts, and the finished task includes the final cost. If the generation fails, the cost is 0 and the held amount returns to your balance in full.
There is no subscription. You top up a prepaid balance (from $10) and it does not expire. For a full breakdown of video costs across models, see How much does AI video cost per second?.
Seedance 2.5 API parameters
These are the Jantaris input fields for video models, as they apply to Seedance 2.5:
| Parameter | Type | Values | Notes |
|---|---|---|---|
prompt | string | free text | Describe subject, action, camera, light and sound |
duration | integer | seconds | Up to 30 for Seedance 2.5 |
resolution | string | 480p, 720p | Use 480p for drafts, 720p for finals |
aspect_ratio | string | 16:9, 9:16, 1:1, 4:3, 3:4 | 9:16 for Reels, Shorts and TikTok |
image_url | string | public URL | Optional first frame (image-to-video) |
generate_audio | boolean | true / false | Turn off if you add your own soundtrack |
The authoritative list, including any model-specific extras, is the schema endpoint:
curl https://jantaris.com/v1/models/seedance-2.5I check it whenever I upgrade code, because a value the model does not accept returns 422 and nothing is charged.
Seedance 2.5 Python example: text to video
Install requests and put your key in an environment variable:
pip install requests
export AI_API_KEY=sk-jt-...Then:
import os
import time
import requests
API = "https://jantaris.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['AI_API_KEY']}"}
def submit(model, input_, webhook_url=None):
body = {"model": model, "input": input_}
if webhook_url:
body["webhook_url"] = webhook_url
r = requests.post(f"{API}/generate", headers=HEADERS, json=body, timeout=30)
if r.status_code != 202:
err = r.json().get("error", {})
raise RuntimeError(f"{r.status_code} {err.get('type')}: {err.get('message')}")
return r.json()
def wait(task_id, every=5, timeout=1200):
deadline = time.time() + timeout
while time.time() < deadline:
t = requests.get(f"{API}/tasks/{task_id}", headers=HEADERS, timeout=30).json()
if t["status"] in ("succeeded", "failed"):
return t
time.sleep(every)
raise TimeoutError(task_id)
def download(url, path):
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
task = submit("seedance-2.5", {
"prompt": (
"Handheld close-up of a street food vendor flipping noodles in a wok at night, "
"steam and flames, neon signs in the background, sizzling sound and crowd chatter"
),
"duration": 10,
"resolution": "720p",
"aspect_ratio": "9:16",
"generate_audio": True,
})
print("task:", task["id"], "estimated:", task["estimated_cost"])
result = wait(task["id"])
if result["status"] == "succeeded":
video = result["output"][0]
download(video["url"], f"{task['id']}.mp4")
print("saved", video["width"], "x", video["height"], "cost:", result["cost"])
else:
print("failed, nothing charged:", result["error"])Download the file as soon as the task succeeds. Output links are valid for a limited time, so treat them as a pickup point, not storage.
Image-to-video with a first frame
When I need control over composition, product placement or a character's look, I generate the first frame as an image and hand it to Seedance. The image_url must be a public HTTPS URL.
task = submit("seedance-2.5", {
"prompt": "The camera slowly orbits the sneaker as dust particles drift in warm backlight",
"image_url": "https://example.com/frames/sneaker-hero.png",
"duration": 6,
"resolution": "720p",
"aspect_ratio": "16:9",
"generate_audio": False,
})A good first frame does most of the work. I usually make it with Nano Banana Pro or FLUX.2 [pro] at the same aspect ratio as the video, which avoids awkward cropping. The image side is covered in Generate AI images with Python.
Webhooks instead of polling
Polling is fine for a script. For a web app, pass a webhook_url and Jantaris will POST the finished task to it, the same JSON you would get from GET /v1/tasks/{id}.
task = submit(
"seedance-2.5",
{"prompt": "Time-lapse of clouds over a mountain lake", "duration": 8, "resolution": "720p"},
webhook_url="https://yourapp.example.com/hooks/video",
)Every webhook carries an x-jantaris-signature header: a hex HMAC-SHA256 of the raw request body, keyed with the webhook secret from your dashboard. Verify it before trusting the payload:
import hashlib
import hmac
def is_valid(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")The full receiver, with retries and a fallback poller, is in Building an AI video pipeline with async tasks and webhooks and the webhooks docs.
Generating several variations in parallel
Video models are non-deterministic. I rarely keep the first take, so I submit three or four at once and pick the best one. Since tasks are async, parallel submission is just a loop:
prompt = "A paper boat drifting down a rain-soaked street at dusk, slow dolly-in"
tasks = [
submit("seedance-2.5", {"prompt": prompt, "duration": 5, "resolution": "480p", "aspect_ratio": "16:9"})
for _ in range(4)
]
results = [wait(t["id"]) for t in tasks]
for r in results:
print(r["id"], r["status"], r["output"][0]["url"] if r["status"] == "succeeded" else r["error"])My routine: drafts at 480p and short duration, pick the winner, then render the final at 720p with the full length. Drafts cost a fraction of the final, and failed drafts cost nothing.
Handling Seedance API errors
| Status | Type of problem | Fix |
|---|---|---|
| 401 | Wrong key | Check AI_API_KEY |
| 402 | Balance too low for estimated_cost | Top up on pricing, then resubmit |
| 422 | Invalid input (duration, resolution, ratio) | Read error.message, compare with the schema |
| 429 | Too many requests | Exponential backoff |
| 502 | Upstream error | Retry after a pause |
A task can also finish with status: "failed", for example if the model's content filter rejects the prompt. The error field explains why and the charge is 0.
Prompt tips that worked for me
Seedance 2.5 responds well to prompts written like a shot description. The order I use:
- Shot and camera: "handheld close-up", "slow aerial push-in", "static wide shot"
- Subject and action: who does what, in one or two clauses
- Setting and light: time of day, light source, weather
- Sound: if
generate_audiois on, name the sounds you want to hear ("rain on a tin roof, distant traffic") - Style: only if needed, one phrase
Short, concrete prompts beat long lists of adjectives. If a take drifts, change one element at a time so you learn what the model reacts to.
How Seedance 2.5 compares
If you are choosing a video model, Best AI video generation APIs in 2026 puts Seedance 2.5 next to Kling 3.0 and Veo 3.1 (Quality). Short version: Seedance wins on single-take length and referencing, while Veo 3.1 and Kling 3.0 go higher on resolution, and Kling 3.0 handles several shots described in one prompt.
To try it without code, open Studio. It uses the same balance as the API.
FAQ
How long can a Seedance 2.5 video be?
ByteDance states up to 30 seconds per generation, and the vendor's API docs list a range of 4 to 30 seconds. Set it with the duration parameter.
How much does the Seedance 2.5 API cost?
It is billed per second of output. The current Jantaris rate is from $0.261 per second, and each request returns estimated_cost before it runs. Failed generations are not charged.
Does Seedance 2.5 generate audio?
Yes. Audio is generated in the same pass as the video. Set generate_audio to false if you plan to add your own soundtrack.
What resolution does Seedance 2.5 support?
ByteDance's API documentation lists 480p and 720p. Check GET /v1/models/seedance-2.5 for the values Jantaris accepts right now.
Can I use an image as the first frame?
Yes. Pass a public image URL in image_url and describe the motion in prompt. Match the image's aspect ratio to aspect_ratio for the cleanest result.












