Seedance 2.5 API Quickstart: Generate Your First 30-Second Video in 10 Minutes
A code-first Seedance 2.5 API tutorial: auth, your first call, every parameter, Python and Node code, plus real measured costs and latency.
Most video generation APIs cap a single call somewhere around 8 or 10 seconds. Want more than that, you stitch clips together and hope the cuts hide the seams. Seedance 2.5 takes a duration integer up to 30 and hands back one continuous clip with synchronized native audio. That one detail changes how you structure a video pipeline.
This is the code-first Seedance 2.5 API tutorial I wanted when I started: no positioning, no feature grid, just the call, the parameters that actually matter, and what it costs when the invoice lands. By the end you will know exactly how to put a 30 second clip on disk.
Every cost figure below is a billed number read off my own Segmind account, not a rate card estimate. Where I am quoting a spec rather than something I measured, I say so.
What you need before your first call
Three things, and the third one is where people lose twenty minutes.
An API key. Grab it from your Segmind dashboard. It goes in an x-api-key header on every request. There is no OAuth dance and no session token.
Python 3 with requests, or Node 18 or newer. Node 18 has fetch built in, so there is nothing to install on that side.
Enough balance to clear the reservation. This is the one that catches people. Segmind reserves the model's average cost up front, not the price of the specific request you are making. For Seedance 2.5 that average sits at roughly $1.90. If your balance is under that floor, you get back HTTP 406: insufficient balance for the reservation, and you get it even when the clip you asked for would only have cost $0.53. I hit this exact wall earlier today on a nearly empty key. Fund for the reservation, not for the invoice.
Your first call
The minimum viable request body is a single field. Everything else has a default.
import requests
resp = requests.post(
"https://api.segmind.com/v1/seedance-2.5",
headers={"x-api-key": "YOUR_API_KEY"},
json={
"prompt": "A barista pulls a shot of espresso on a brushed steel machine. "
"Steam curls through warm morning light from a window on the left. "
"Slow push in, shallow depth of field, photorealistic, "
"ambient cafe sound and the hiss of the steam wand.",
"duration": 10,
"resolution": "720p",
"generate_audio": True,
},
timeout=600,
)
resp.raise_for_status()
with open("clip.mp4", "wb") as f:
f.write(resp.content)
print("billed:", resp.headers.get("x-cost"))
That is the whole integration. Seedance 2.5 on Segmind is a synchronous endpoint: you POST, the connection stays open while the model works, and the response body is the finished MP4. There is no job ID, no polling loop, and no webhook to stand up.
The tradeoff is that the request is long-lived. A 10 second clip took between 81 and 116 seconds in my runs, and longer clips take longer still. The timeout=600 above is not paranoia. The default timeout in most HTTP clients will tear down the connection long before the model is done, and you will have paid for a clip you never received.
The response is a raw MP4, not JSON
The most common first mistake is calling resp.json() and getting a decode error. On success the body is binary video. Write resp.content straight to a file.
On failure, though, the body is JSON. So the useful pattern is to branch on the status code rather than assume either shape:
if resp.status_code != 200:
print(resp.status_code, resp.json()) # error details live here
else:
open("clip.mp4", "wb").write(resp.content)
The other thing worth reading off a successful response is the x-cost header, which carries what the call actually billed. That number is the ground truth for spend. If you are building anything with a budget attached, log it per call from day one.
The parameters that change your output
Here is the full input surface, taken from the Seedance 2.5 model spec, with the defaults spelled out. The defaults matter more than usual on this model because two of them will surprise you.
| Parameter | Type | Default | Notes |
|---|---|---|---|
prompt | string | required | The only mandatory field. |
duration | integer | 5 | Range 4 to 30, in seconds. |
resolution | string | 720p | Either 480p or 720p. |
aspect_ratio | string | 16:9 | 16:9, 9:16, 1:1, 4:3, 3:4, 21:9, adaptive. |
generate_audio | boolean | false | Native synchronized audio. Off unless you ask. |
seed | integer | 42 | Range -1 to 2147483647. Fixed by default. |
first_frame_url | URL | none | Start the clip from your own image. |
last_frame_url | URL | none | Target an ending frame. |
reference_images | array | [] | Subject or style references. |
reference_videos | array | [] | Video references. |
reference_audios | array | [] | Audio references. |
return_last_frame | boolean | false | Hand back the final frame for chaining. |
bitrate_mode | string | standard | standard or high. |
skip_moderation | boolean | false | Bypass the content check. |
The two defaults that will catch you. First, generate_audio is false. The headline capability of this model is synchronized native audio, and you do not get it unless you explicitly turn it on. Second, seed defaults to 42, a fixed value rather than a random one. Two identical request bodies return the same clip. That is excellent for reproducibility and confusing if you are hammering the same prompt expecting variety. Set seed to -1 when you want the roll of the dice.
720p is a pixel budget, not a height. Worth knowing before you build layout around the output. The resolution setting works out to roughly 921,000 pixels, distributed according to your aspect ratio. Asking for 21:9 returned 1470x630. Asking for 9:16 returned 720x1280. Only the square-ish ratios land on a literal 720.
Going to 30 seconds
The long-form call is the same request with one number changed:
payload = {
"prompt": (
"A drone rises slowly over a coastal fishing village at dawn. "
"Boats rock in the harbour, gulls cross the frame, and mist burns "
"off the water as the sun clears the headland. The shot drifts "
"inland over tiled rooftops and a waking market street. "
"Cinematic, photorealistic, continuous unbroken camera move, "
"ambient harbour sound and distant gulls."
),
"duration": 30,
"resolution": "720p",
"aspect_ratio": "16:9",
"generate_audio": True,
"seed": -1,
}
On my account a 30 second 720p 16:9 clip with audio billed $7.118433. That is the real number off the header, and it came in about 0.68% under the published rate card, which is the sort of rounding drift you would expect from token-based billing.
Two practical notes on long clips. Give the prompt enough structure to fill the time: a 30 second clip driven by a one-line prompt tends to drift, while a prompt that describes a camera move with a beginning, middle and end gives the model something to hold onto for the full duration. And set your client timeout generously. My longest observed generation ran 224 seconds, so a 600 second ceiling leaves comfortable headroom.
Starting from your own image
Text to video is one entry point. The other is handing Seedance 2.5 a frame and asking it to move. Point first_frame_url at a publicly reachable image URL and the clip starts from that exact frame:
payload = {
"prompt": "The camera pushes in slowly on the bottle as morning light "
"shifts across the stone. Dust motes drift through the beam. "
"Subtle, premium, product film. Quiet ambient room tone.",
"first_frame_url": "https://your-bucket.s3.amazonaws.com/product.jpg",
"duration": 10,
"aspect_ratio": "9:16",
"resolution": "720p",
"generate_audio": True,
}
The URL has to be publicly fetchable. Segmind's servers pull the image, so a presigned link that expires or a file behind auth will fail. The same rule applies to last_frame_url, reference_images, reference_videos and reference_audios.
If you are chaining clips into something longer than 30 seconds, set return_last_frame to true and feed the returned frame into the next call's first_frame_url. That keeps continuity across the cut far better than restarting from a text prompt.
The same call in Node
Node 18 and up needs no dependencies. The only wrinkle is converting the response to a buffer before writing:
import fs from "node:fs/promises";
const res = await fetch("https://api.segmind.com/v1/seedance-2.5", {
method: "POST",
headers: {
"x-api-key": process.env.SEGMIND_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "A barista pulls a shot of espresso, steam curling through morning light.",
duration: 10,
resolution: "720p",
generate_audio: true,
}),
});
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
console.log("billed:", res.headers.get("x-cost"));
await fs.writeFile("clip.mp4", Buffer.from(await res.arrayBuffer()));
And the curl version, for when you just want to confirm the key works:
curl -X POST https://api.segmind.com/v1/seedance-2.5 \
-H "x-api-key: $SEGMIND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"A cat naps on a sunlit windowsill","duration":5}' \
--output clip.mp4
What it actually costs
These are billed figures from my own runs, not projections. Every row is 16:9 with audio on unless noted.
| Duration | Resolution | Billed | Per second |
|---|---|---|---|
| 5s | 480p | $0.531354 | $0.1063 |
| 5s | 720p | $1.194633 | $0.2389 |
| 10s | 720p | $2.379393 | $0.2379 |
| 15s | 720p | $3.564153 | $0.2376 |
| 30s | 720p | $7.118433 | $0.2373 |
Three things fall out of that table.
Pricing is essentially linear in duration. The per second rate drifts down by a fraction of a percent as clips get longer, but for budgeting purposes 720p costs about $0.238 per second of output and 480p about $0.106.
480p costs roughly 45% of 720p. If you are iterating on prompt and composition, draft at 480p and only spend 720p money once the shot is right. On a 30 second clip that is the difference between about $3.20 and $7.12 per attempt.
Audio is free. The rate held flat across the generate_audio toggle and across aspect ratios in my testing. Cost tracks resolution and duration, nothing else. There is no reason to leave audio off unless you specifically want a silent clip.
One more piece of good news for anyone iterating on parameters: failed generations bill $0.00. I have confirmed that across several rejected requests. Getting the parameter space wrong costs you latency, not money.
How long you will wait
This is the number I would most like to give you cleanly, and I cannot, because it is noisier than it looks. Across my runs, 10 second clips came back in 81 and 116 seconds. 15 second clips came back in 160 and 224 seconds. The spread between two requests of identical length was wider than the gap between different lengths.
The honest read: wall clock tracks queue load more than it tracks duration. Budget two to four minutes per clip, set a long client timeout, and do not build anything that assumes generation time is a function of the duration you asked for.
If you are generating several clips, fire them sequentially rather than in parallel from a single process. Parallel fires from one process have dropped silently on me, and a dropped fire is far more annoying to debug than a slow one.
Gotchas worth knowing up front
The 406 is about your balance, not your key. A valid key with a thin balance returns 406, not 401. If you see it, top up rather than regenerating credentials.
The MP4 is not web-optimised. The moov atom lands after mdat in the returned file, which means no fast start. A browser streaming it progressively may buffer before playback begins. If you are serving these directly, remux with faststart before you upload.
Files are larger than you might plan for. A 10 second 720p clip ran about 9 to 10 MB and a 15 second clip about 19 to 22 MB, so budget roughly 35 MB for a 30 second clip. That matters for storage and for anything you are pushing over mobile.
FAQ
Is there a working Seedance 2.5 API tutorial with real code?
This Seedance 2.5 API tutorial includes complete Python, Node and curl examples above. Each one is a single POST to https://api.segmind.com/v1/seedance-2.5 with an x-api-key header, returning a finished MP4.
How long can a Seedance 2.5 video be?
Between 4 and 30 seconds in a single call, passed as an integer in the duration field. The default is 5 seconds. For anything longer than 30 seconds, chain calls using return_last_frame.
How much does a 30 second Seedance 2.5 video cost?
A 30 second 720p clip with audio billed $7.118433 on my account. At 480p the same length would run closer to $3.20. Pricing is effectively linear at about $0.238 per second at 720p.
Does Seedance 2.5 generate audio automatically?
No. generate_audio defaults to false, so you have to set it to true explicitly. It costs nothing extra, and the audio is generated synchronized to the video rather than dubbed on afterwards.
Why is the Seedance 2.5 API returning HTTP 406?
Your balance is below the reservation floor. Segmind reserves the model's average cost, around $1.90, before running your request. You will see 406 even if your specific clip would cost less than that.
Can I use my own image as the first frame?
Yes. Pass a publicly reachable image URL in first_frame_url and the clip starts from that frame. Presigned or authenticated URLs will fail because Segmind's servers fetch the file directly.
Where to go from here
The integration itself is about ten lines of code. The real work is prompt structure and knowing which defaults to override: turn generate_audio on, set seed to -1 unless you want reproducibility, draft at 480p and finish at 720p, and give your HTTP client a timeout long enough to survive a four minute generation.
Once those are in place, the 30 second ceiling is what makes this model interesting. A single continuous shot with synchronized audio is a different production primitive than four stitched clips, and it is worth restructuring a pipeline around.
The full parameter reference and current pricing live on the Seedance 2.5 model page. Fund the key past the reservation floor, run the first snippet in this post, and you will have a clip on disk inside ten minutes.