Video generation
Text-to-video, image-to-video, first/last-frame and reference-to-video across Veo 3.1, Kling, Seedance, Sora, Wan and Runway — one async job API, charged on delivery.
Every video model in the catalog runs on one async surface: POST /v1/llm/videos/jobs
returns a job_id in under a second, and you poll
GET /v1/llm/videos/jobs/{job_id} until the status is terminal. There is no
synchronous video endpoint — nothing to migrate, no connection to lose.
You are charged once, on the first poll that returns a video.url; later
polls replay the completed payload until the job record expires, 2 hours
after kickoff. A failed or timed-out job costs nothing.
Request body
Every field, which models read it, and what a wrong value does.
Duration matrix
Each family accepts its own set of clip lengths. Veo does not take 5.
Reference media
URLs and data URIs, hosting rules, per-model reference caps.
Quick start
The catalog covers text-to-video (t2v — prompt only), image-to-video
(i2v — pass image_url as the start frame), first/last-frame
interpolation, reference-to-video, and video-to-video motion control
(v2v — pass video_url plus image_url for the conditioning frame).
Avatar SKUs take an audio reference URL in the request body.
import time, requests
BASE = "https://api.gpuniq.com/v1/llm"
HEADERS = {"X-API-Key": "gpuniq_your_key"}
# 1. Kickoff
start = requests.post(
f"{BASE}/videos/jobs",
headers=HEADERS,
json={
"model": "kling-2-6",
"prompt": "A small black cat slowly turns toward the camera at golden hour",
"duration": 5,
"audio": False, # opt-in, doubles price on Kling 2.6 / 3.0
"resolution": "1080p", # 720p | 1080p | 4k (where supported)
},
).json()
job_id = start["data"]["job_id"]
print(f"job: {job_id}, est cost: ${start['data']['estimated_cost_usd']}")
# 2. Poll — video models deliver in 30-90s; budget 5 minutes for the slowest variants
deadline = time.time() + 300
while time.time() < deadline:
time.sleep(3)
r = requests.get(f"{BASE}/videos/jobs/{job_id}", headers=HEADERS).json()
d = r["data"]
if d["status"] == "completed":
print(f"video: {d['video']['url']}")
print(f"cost: ${d['cost_usd']}, balance: ${d['balance_usd']}")
break
if d["status"] == "failed":
print("failed:", d.get("error"))
break
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
model | string | yes | Slug from the catalog. |
prompt | string | yes | Up to 10 000 characters; up to 20 000 on seedance-2 and seedance-2-5 (10 000 for video_task edit / extend jobs). |
duration | int | no | Seconds. Every model has its own legal set — see the duration matrix below. An out-of-range value is a 400, not a silent clamp. |
aspect_ratio | string | no | 16:9 (default), 9:16, 1:1 where supported. The Seedance 2 line (incl. seedance-2-5) additionally accepts auto, 21:9, 4:3, 3:4. |
image_url | string | no | https URL or data URI — enables image-to-video (start frame). |
last_frame_url | string | no | https URL or data URI — end frame for first/last-frame interpolation. Supported on Kling 2.1-Pro / 2.6 / 3.0 / O3, Veo 3.1 (all tiers) and the whole Seedance 2 line (seedance-2, seedance-2-5, -fast, -mini). Requires image_url. The first image is always the FIRST frame, the second the LAST — order matters. |
video_url | string | no | https URL or data: URI — reference clip for motion-control v2v variants and Seedance 2 line reference-to-video. Links the host serves as a non-video Content-Type are re-hosted automatically. |
video_urls | array | no | Reference clips for reference-to-video (Seedance 2 line) — https URLs or data: URIs, up to 3 on seedance-2 / -fast / -mini and up to 10 on seedance-2-5. A data: URI, a share link that serves the file as application/octet-stream, or a link that redirects (shorteners, download endpoints) is re-hosted as a direct video/mp4 URL before dispatch — some upstreams refuse each of those shapes outright. Single-reference models use the first entry. |
reference_image_urls | array | no | Seedance 2 line, Kling O3 and Veo 3.1 fast/lite — https URLs / data URIs of subject or style reference images (JPG/PNG/WebP). Up to 30 on seedance-2-5, 9 on the rest of the Seedance 2 line, 4 on Kling O3, 3 on Veo 3.1 fast/lite. Switches the job to reference-to-video mode. |
audio_url | string | no | Seedance 2 line only — reference audio track (MP3/WAV). Requires at least one image or video reference in the same request. |
audio_urls | array | no | Seedance 2 line only — up to 10 tracks ≤30 s combined on seedance-2-5, up to 3 tracks ≤15 s combined elsewhere; merged with audio_url. |
resolution | string | no | Seedance 2: 480p / 720p (default) / 1080p / 4k, priced per second per resolution. seedance-2-5: 480p / 720p (default) / 1080p — a 480p render can also be finished at 1080p later, see 1080p on Seedance 2.5. -fast / -mini: 480p / 720p (default) only. Kling 3.0 / O3: 720p / 1080p (default) / 4k. Kling 2.6 / 3.0 Motion Control: 720p / 1080p (default). Veo 3.1: 720p / 1080p / 4k. Kling 2.1 / 2.5 Turbo Pro: 1080p only. Default is 1080p for every Kling/Veo SKU when the field is omitted — the Seedance 2 line is the exception at 720p. |
audio | bool | no | Default false. Kling 2.6 / 3.0 double the price when true; on the Seedance 2 line it generates the clip's audio track (no surcharge). |
mode | string | no | standard / pro (default) / master for Kling 2.1; turbo for 2.5 Turbo Pro. |
character_orientation | string | no | Kling 2.6 / 3.0 Motion Control only. video (default) — the output follows the reference clip's framing, clip up to 30s; image — the output follows the character image, clip up to 10s. Other models ignore it. |
seed | int | no | seedance-2 only — integer in [-1, 4294967295]; omit or pass -1 for a random seed. Reusing a seed with an otherwise identical request pulls the render toward the earlier one; it does not reproduce it frame for frame. Accepted but inert on seedance-2-5 / -fast / -mini; other models ignore the field. |
output_format | string | no | Container of the delivered file: mp4 (default, H.264), mov or webm. Not every route encodes every container — mov and webm are served on seedance-2-5, seedance-2 / -fast / -mini, kling-3-0, kling-o3-video, kling-2-6 and kling-2-5-turbo-pro; seedance-2-5 additionally serves mov on its second route. Every other model is MP4-only. A container the model's routes cannot encode is a 400 before the job starts — the field is never dropped silently, because a clip in the wrong container is a clip you would pay for twice. The kickoff receipt echoes it in inputs.output_format. |
output_quality | int | no | Currently ignored (since 2026-09-22). The field is still accepted, so existing integrations do not break, but it no longer affects the render or the routing: only one of the routes behind each model can set an encoder quality, and honouring the field removed every other route — which made jobs fail to start on some model + aspect-ratio combinations. Renders use the upstream default of 95, already the top of the visually lossless range. The kickoff receipt repeats this in inputs.notes. No route exposes a bitrate or a codec either: bitrate, bitrate_mode and codec are refused with a hint rather than ignored. If a clip looks pixelated in the downloaded file at 100 % on a paused frame, the limit is the render itself — raise resolution (1080p / 4k where the model has it), not the encode. |
Vendor spellings are accepted, unknown ones are refused. The media
fields also answer to the names the model vendors use —
reference_video_urls, reference_videos, reference_audio_urls,
first_frame_url, end_frame_url, source_video_url,
duration_seconds, and the camelCase of any field above. A field name
we don't recognise that looks like an attachment is a 400 naming
it: an input that silently fails to attach costs you a render, so we
refuse the request instead. Each kickoff also returns data.inputs —
the mode and the count of references that reached the model.
Duration by model
duration is not a free integer. Each family accepts its own set and
anything outside it comes back as a 400 naming the legal values — we do
not round to the nearest supported length, because silently rendering
(and billing) 8 seconds when you asked for 5 is worse than a refusal.
| Model | Legal duration | Default |
|---|---|---|
veo-3-1-lite / veo-3-1-fast / veo-3-1-quality | 4, 6 or 8 — nothing else | 8 |
veo-3-1-fast / -lite with reference_image_urls | 8 only | 8 |
kling-2-1, kling-2-5-turbo-pro, kling-2-6 | 5 or 10 | 5 |
kling-3-0 | 5 or 10 (multi-shot to 15) | 5 |
kling-o3-video | any integer 3–15 | 5 |
kling-2-6-motion-control, kling-3-0-motion-control | 5 or 10 | 5 |
kling-avatar-pro, kling-avatar-standard | any integer 1–15 | 5 |
seedance-2, seedance-2-fast, seedance-2-mini | any integer 4–15 | 5 |
seedance-2-5 | any integer 4–30 | 5 |
seedance-1-0-pro, seedance-1-5-pro | 5 | 5 |
sora-2-video, sora-2-pro-video | 10 | 10 |
sora-2-official, sora-2-pro-official | 8 | 8 |
Veo does not take duration: 5. It is the single most common 400 on
this surface — 5 is the default nearly everywhere else in the catalog,
so it gets copied across from a Kling example. Veo's native set is
4 / 6 / 8; ask for 5 and you get
invalid_request: Veo 3.1 supports durations of 4, 6 or 8 seconds (got 5).
Reference media: what format to send
Every media field takes a URL string, never an uploaded file part — there is no multipart endpoint. Two forms are accepted:
| Form | Looks like | Where it works |
|---|---|---|
| Public HTTPS URL | https://cdn.example.com/ref.jpg | every media field |
| Data URI (base64 inline) | data:image/jpeg;base64,/9j/4AAQ… | every media field |
Upstream renderers only accept URLs, so a data: URI on a video or audio
field is materialised into a real link on our side before dispatch. You
do not have to host anything yourself to use a reference clip.
Hosted links are re-hosted when the host misdescribes them. Upstream
validators judge a reference clip by the Content-Type the host
declares, not by the bytes — so a perfectly good MP4 served as
application/octet-stream used to be refused on sight. GPUniq now probes
that header and, when it is not a video/* type, fetches the file and
re-serves it correctly before dispatch.
Google Drive / Dropbox / OneDrive share links now work. Drive
serves files as application/octet-stream with
X-Content-Type-Options: nosniff and the original filename
(IMG_3752.MOV), which upstream validators reject — this was the single
most common cause of "my motion-control job will not start". Such links
are re-hosted automatically as of 2026-08-15.
The link must still be directly downloadable without signing in —
set the share to "anyone with the link", and use the
uc?export=download&id=… form rather than a /view page. A link that
returns an HTML sign-in page has no file behind it for us to fetch.
Serving a reference from a bucket with the right Content-Type is still
the fastest path: it skips the extra fetch entirely. What the upstream
fetch requires, in the order things usually go wrong:
- A direct link to the file, not to a viewer page. The response must be the bytes themselves.
- No authentication, no redirect chain to a login page. The fetcher is anonymous and has no cookies.
- A media
Content-Type—video/mp4,image/jpeg,audio/mpeg. Handled for you when it is wrong, at the cost of one extra round trip. - A file extension that matches the content where possible — some upstreams sniff the URL path before they sniff the body.
Size and format limits:
| Field | Formats | Limits |
|---|---|---|
image_url, last_frame_url | JPG, PNG, WebP | one image each |
reference_image_urls | JPG, PNG, WebP | ≤30 seedance-2-5 · ≤9 rest of the Seedance 2 line · ≤4 Kling O3 · ≤3 Veo 3.1 fast/lite |
video_url, video_urls | any common container (MP4, WebM, MKV, MOV) | ≤10 clips 2–30 s combined on seedance-2-5; ≤3 clips 2–15 s combined elsewhere; ≤50 MB total |
audio_url, audio_urls | MP3, WAV | ≤10 tracks ≤30 s combined on seedance-2-5; ≤3 tracks ≤15 s combined elsewhere |
Reference video resolution and container are unrestricted: 1080p/4K and non-MP4 sources are transcoded to ≤720p MP4 on our side before dispatch, so send source footage as-is. Keep in mind that a data URI inflates by ~33% over the raw bytes and counts against the request body limit — for anything above a few MB, host it and send a URL.
Which model takes which reference input
Sending a field the chosen model has no input for is a 400 at submit,
not a silent drop. That is deliberate: previously a request carrying
reference_image_urls could render on one attempt and be refused on the
next, depending on which internal route served it. Capability is now
resolved before dispatch, so the same request gets the same answer every
time.
| Model | image_url | last_frame_url | reference_image_urls | video_url | audio_url |
|---|---|---|---|---|---|
seedance-2-5 | ✅ | ✅ | ✅ ≤30 | ✅ ≤10 | ✅ ≤10 |
seedance-2 | ✅ | ✅ | ✅ ≤9 | ✅ ≤3 | ✅ ≤3 |
seedance-2-fast, seedance-2-mini | ✅ | ✅ | ✅ ≤9 | ✅ ≤3 | ✅ ≤3 |
kling-o3-video | ✅ | ✅ | ✅ ≤4 | ✖ | ✖ |
veo-3-1-fast / veo-3-1-lite | ✅ | ✅ | ✅ ≤3 (8 s only) | ✖ | ✖ |
veo-3-1-quality | ✅ | ✅ | ✖ | ✖ | ✖ |
kling-2-6-motion-control / kling-3-0-motion-control | ✅ required | ✖ | ✖ | ✅ required | ✖ |
kling-2-1, kling-2-5-turbo-pro, kling-2-6, kling-3-0 | ✅ | ✅ (2.1/2.6/3.0) | ✖ | ✖ | ✖ |
seedance-1-0-pro, seedance-1-5-pro | ✅ | ✖ | ✖ | ✖ | ✖ |
For subject or style reference on a model with no reference_image_urls
column, use image_url as the start frame — or move to the Seedance 2
line (seedance-2 / seedance-2-5), which has the widest reference
surface in the catalog.
Motion control is the strictest shape here: kling-2-6-motion-control
and kling-3-0-motion-control need both image_url (the character /
subject frame) and video_url (the motion to transfer). Either one alone
is a 400 naming the missing field.
Response
The kickoff returns immediately with the GPUniq job id, the resolved
parameter snapshot, and the cost estimate. Internal routing is
opaque — the same job_id is valid across fallbacks, and the
user-facing price stays stable.
// POST /v1/llm/videos/jobs
{
"job_id": "vid_e93e98c7ca5e4982876b",
"status": "pending",
"model": "kling-2-6",
"estimated_cost_usd": 0.315,
"config": { "resolution": "1080p", "audio": false, "duration": 5, "task": "t2v", "mode": null }
}
// GET /v1/llm/videos/jobs/{job_id} — completed
{
"job_id": "vid_e93e98c7ca5e4982876b",
"status": "completed",
"model": "kling-2-6",
"video": {
"url": "https://cdn.example.com/.../output.mp4",
"stored_url": "https://api.gpuniq.com/v1/llm/media/8xK2p….mp4",
"stored_expires_in_days": 7,
"stored_ready": false
},
"cost_usd": 0.315,
"balance_usd": 9.17825791,
"config": { "resolution": "1080p", "audio": false, "duration": 5, "task": "t2v", "mode": null }
}
The polling endpoint transparently falls back across internal routes if the first attempt fails — your job_id and the user-facing price stay stable across fallbacks. Internal route identifiers are deliberately omitted from the public response; they live only in admin/operator logs.
Where delivered media lives
Every generated image and video is also mirrored to GPUniq storage, and the response tells you where:
| Field | What it is |
|---|---|
video.url / image.b64_json | the original delivery — live immediately, unchanged |
video.stored_url / image.url | a GPUniq-hosted copy at https://api.gpuniq.com/v1/llm/media/{token} |
video.stored_ready | false on the delivering poll while the copy is still being written; true on a later poll once stored_url resolves |
stored_expires_in_days / expires_in_days | how long that copy is kept (7 days by default) |
Why it matters: the upstream video.url points at the rendering
provider's CDN, and its lifetime is set by them, not by us. If you store
that link rather than the bytes, it can stop resolving well before you
expect. stored_url is ours and lives exactly as long as the field says.
This is a delivery buffer, not an archive. Copies are deleted
automatically when the retention window closes and are not
recoverable afterwards. If you need media permanently, download it and
keep it on your own storage — treat stored_url as the safety net that
gets you from "the job finished" to "the bytes are in my bucket", not
as the bucket.
Two practical notes. For images the copy is written before the
response is sent, so url works the moment you receive it. For
videos the copy runs in the background — a clip is tens of megabytes
and we would rather not make the delivering poll wait on it — so
stored_url becomes valid shortly after the response; url is live
immediately either way. Poll the job once more rather than hammering
stored_url: the replayed payload flips video.stored_ready to true when
the copy has landed. And if storage is briefly unavailable the
stored_url / url fields are simply absent: the generation still
succeeds and is still delivered, because a storage hiccup is never
allowed to fail a render you paid for.
Models & pricing
Video models are billed per delivered video or per second of output —
the Headline / video column is the cheapest default configuration, not a
flat rate. The exact amount charged is always in the completion response's
cost_usd.
To know the price of a specific configuration before starting it, send the
kickoff body to POST /v1/llm/videos/jobs/estimate — Kling / Veo are
priced off the very route the kickoff would use, per-second lines off
resolution and duration (reference clips add their input seconds at the
upstream's cap). The catalog's pricing block carries the tables:
per_second_usd by resolution for the per-second lines and
by_resolution_usd per clip for Kling / Veo at the defaults listed in
assumes.
| Family | Slug | Headline / video | Notes |
|---|---|---|---|
| OpenAI Sora 2 | sora-2-video | $0.600 | Sora 2, default 10s |
| OpenAI Sora 2 Pro | sora-2-pro-video | $1.000 | Premium quality, 10s |
| Sora 2 Official | sora-2-official | $0.480 | 8s, official API |
| Sora 2 Pro Official | sora-2-pro-official | $0.560 | 8s 1080p, official API |
| Google Veo 3.1 Lite | veo-3-1-lite | from $0.30 | 720p / 1080p / 4K, 4–8s; i2v, first/last frame, reference-to-video |
| Google Veo 3.1 Fast | veo-3-1-fast | from $0.60 | 720p / 1080p / 4K, 4–8s; i2v, first/last frame, reference-to-video |
| Google Veo 3.1 Quality | veo-3-1-quality | from $2.50 | Flagship; 720p / 1080p / 4K, 4–8s; i2v, first/last frame |
| Kling 2.1 Pro | kling-2-1 | $0.405 | Standard / Pro / Master tiers, 5s or 10s, i2v |
| Kling 2.5 Turbo Pro | kling-2-5-turbo-pro | $0.315 | 5s or 10s, t2v / i2v |
| Kling 2.6 | kling-2-6 | $0.315 | Optional audio, 5s or 10s, t2v / i2v |
| Kling 3.0 | kling-3-0 | $0.504 | 720p / 1080p / 4K, audio, multi-shot to 15s |
| Kling O3 (Video) | kling-o3-video | from $0.076 / s | 720p / 1080p / 4K, native audio, 3–15s; t2v / i2v, first+last frame, reference images (≤4) |
| Kling 2.6 Motion Control | kling-2-6-motion-control | $0.504 | 720p / 1080p video-to-video |
| Kling 3.0 Motion Control | kling-3-0-motion-control | $0.756 | 720p / 1080p video-to-video |
| Kling AI Avatar Pro | kling-avatar-pro | $1.035 | 1080p lip-sync, up to 15s |
| Kling AI Avatar Standard | kling-avatar-standard | $0.506 | 720p lip-sync, up to 15s |
| Hailuo 02 | hailuo-02 | $0.200 | 768p, 6s default |
| Hailuo 2.3 | hailuo-2-3 | $0.350 | 768p 6s |
| Seedance 1.0 Pro | seedance-1-0-pro | $0.210 | ByteDance 720p, 5s, t2v + i2v (per video) |
| Seedance 1.5 Pro | seedance-1-5-pro | $0.160 | ByteDance 720p, 5s, t2v + i2v (per video) |
| Seedance 2 | seedance-2 | $0.122–$1.40 / s | ByteDance 480p–4k, billed per second, rate depends on resolution (720p: $0.272/s, 5s ≈ $1.36) |
| Seedance 2.5 | seedance-2-5 | $0.198–$0.86 / s | ByteDance 480p / 720p / 1080p, 4–30s, billed per second (720p: $0.426/s, 5s ≈ $2.13); first/last frame, reference images + video + audio |
| Seedance 2 Fast | seedance-2-fast | $0.13–$0.27 / s | Faster, cheaper Seedance 2 line — 480p / 720p, 4–15s, billed per second |
| Seedance 2 Mini | seedance-2-mini | $0.08–$0.17 / s | Smallest Seedance 2 line — 480p / 720p, 4–15s, billed per second |
| Alibaba Wan 2.2 Fast | wan-2-2-fast | $0.120 | 720p fast tier |
| Alibaba Wan 2.5 | wan-2-5 | $0.600 | 720p 5s |
| Alibaba Wan 2.6 | wan-2-6 | $0.800 | 720p 5s flagship |
| Wan Animate | wan-animate | $0.150 | 720p animation |
| Happy Horse | happy-horse | $0.160 | 720p |
| Grok Imagine Video | grok-imagine-video | $0.300 | xAI video, 6s |
| Grok Imagine Video 1.5 | grok-imagine-video-1-5-preview | from $0.08 / s | xAI, billed per second, rate by resolution |
| Runway Gen-4.5 | runway-gen-4-5 | $0.750 | Runway flagship 5s |
Kling SKUs are billed at −10% off the official public price. The
headline above is the cheapest default configuration (1080p / no
audio / 5s / Pro tier). Audio, longer duration, 4K, and Master tier
scale the price linearly off the underlying reference rate × 0.9 — the
exact cost is returned in the cost_usd field of the completion
response. A 10% margin floor against the upstream supplier
guarantees we never bill below source cost, so on a provider
fallback the price may rise by 1-3%.
Veo 3.1 (Google)
All three Veo tiers are billed per video, by resolution. Duration can be 4, 6 or 8 seconds (default 8) and does not change the price.
| Resolution | Lite | Fast | Quality |
|---|---|---|---|
720p | $0.30 | $0.60 | $2.50 |
1080p (default) | $0.35 | $0.65 | $2.55 |
4k | $1.50 | $1.80 | $3.70 |
Omitting resolution gives you 1080p, not 720p — the video job
surface defaults to 1080p across the whole Kling/Veo family, so the
cheapest row is opt-in. Pass "resolution": "720p" explicitly if that
is what you are budgeting for. The kickoff response echoes the resolved
value in config.resolution and prices it in estimated_cost_usd, so
you can always check before the render runs.
- First + last frame —
image_url(first frame) +last_frame_url(last frame) on all three tiers; the clip interpolates between the two frames and the order is honoured. - Reference-to-video — up to 3 reference images via
reference_image_urlsonveo-3-1-fast/veo-3-1-lite(8-second clips only; not available on Quality). Mutually exclusive withlast_frame_url. aspect_ratio:16:9(default),9:16, orauto(follows the input image geometry).- On a provider fallback the charge follows the serving upstream's
rate — the exact amount is always in the completion
cost_usd.
Kling O3
Kling O3 is billed per second, by resolution and audio flag, for clips of 3–15 seconds (default 5). Prompts are capped at 2500 characters.
| Resolution | Audio off | Audio on |
|---|---|---|
720p | $0.0756 / s | $0.1008 / s |
1080p (default) | $0.1008 / s | $0.1260 / s |
4k | $0.3780 / s | $0.3780 / s |
- Text-to-video — prompt only;
aspect_ratiois16:9(default),9:16or1:1. - Image-to-video —
image_url(start frame), optionally withlast_frame_url(end frame). The aspect ratio follows the frame. - Reference-to-video — up to 4 reference images via
reference_image_urls, which define the subject/style.image_url/last_frame_urlmay ride along as start / end anchors. - No reference video.
video_url/video_urlsare rejected with a 400 on this model: the upstream accepts a clip at submit and then fails the render. For video-to-video usekling-2-6-motion-control/kling-3-0-motion-control.
Seedance (ByteDance)
Seedance is ByteDance's text-to-video / image-to-video family — fast, photoreal clips, well suited to product shots, social content, and image-to-video animation of a still frame. Six SKUs are in the catalog, and they do not all price the same way, so read this before budgeting a batch.
| Slug | Model | Resolution | Duration (default) | Modes | Billing |
|---|---|---|---|---|---|
seedance-1-0-pro | Seedance 1.0 Pro | 720p | 5s | t2v, i2v | Flat $0.210 / video |
seedance-1-5-pro | Seedance 1.5 Pro | 720p | 5s | t2v, i2v | Flat $0.160 / video |
seedance-2 | Seedance 2 | 480p / 720p / 1080p / 4k | 4–15s (5) | t2v, i2v, first/last-frame, ref-to-video (image + video + audio refs) | Per second, by resolution — see below |
seedance-2-5 | Seedance 2.5 | 480p / 720p / 1080p (or a 480p draft → 1080p final) | 4–30s (5) | t2v, i2v, first/last-frame, ref-to-video (image + video + audio refs) | Per second, by resolution — see below |
seedance-2-fast | Seedance 2 Fast | 480p / 720p | 4–15s (5) | t2v, i2v, first/last-frame, ref-to-video | Per second, by resolution |
seedance-2-mini | Seedance 2 Mini | 480p / 720p | 4–15s (5) | t2v, i2v, first/last-frame, ref-to-video | Per second, by resolution |
The whole Seedance 2 line is billed per second of generated video, not per clip — and the rate depends on the resolution you pick.
seedance-2:
| Resolution | $/second | $/second with reference video(s) |
|---|---|---|
480p | $0.122 | $0.073 |
720p (default) | $0.272 | $0.163 |
1080p | $0.612 | $0.368 |
4k | $1.40 | $0.84 |
seedance-2-5:
| Resolution | $/second | $/second with reference video(s) |
|---|---|---|
480p | $0.198 | $0.119 |
720p (default) | $0.426 | $0.255 |
1080p | $0.86 | $1.03 — output seconds only |
1080p on seedance-2-5 is reached two ways at the same $0.86/s: a
direct "resolution": "1080p" render, or the
final of a 480p draft — then you
pay for both jobs, $0.198 + $0.86 = $1.058 per second at list
price. A 1080p job with reference video bills its output seconds
only — the reference clip adds nothing at this tier. There is no 4k
on this SKU.
seedance-2-fast / seedance-2-mini:
| Resolution | fast $/s | fast + ref video | mini $/s | mini + ref video |
|---|---|---|---|---|
480p | $0.13 | $0.075 | $0.08 | $0.05 |
720p (default) | $0.27 | $0.165 | $0.17 | $0.104 |
A 5-second 720p seedance-2 generation (the default) costs
5 × $0.272 = $1.36; the same clip on seedance-2-5 is
5 × $0.426 = $2.13. Reference-to-video jobs use the lower
per-second rate but are billed for the output duration plus the
reference-clip seconds (the upstream meters reference footage too).
The clip length isn't knowable at submit, so the kickoff
estimated_cost_usd assumes the worst case — the input cap, 15 s
on seedance-2 / -fast / -mini and 30 s on seedance-2-5 —
and the final cost_usd is recomputed from the measured clip length,
so short references settle cheaper than the estimate. A 5s 720p
seedance-2-5 ref-to-video job estimates (5 + 30) × $0.255 =
$8.93 and bills (5 + your actual clip seconds) × $0.255. The two
-pro SKUs, by contrast, are a flat per-video price regardless of
duration. Always read the cost_usd field of the completion response
for the exact amount charged.
Choosing between them:
seedance-1-5-pro— cheapest ($0.160/clip), newest of the "pro" tier. Best default for short 720p clips where you want a fixed, predictable price.seedance-1-0-pro— the previous pro model ($0.210/clip); keep using it only if you've tuned prompts against it.seedance-2— the high-quality tier with the widest resolution menu (up to 4k). Priced per second, so it scales with clip length.seedance-2-5— the newest ByteDance line and the flagship of the family. Best motion coherence and the only SKU that renders up to 30 seconds in one job. It renders 480p / 720p / 1080p directly, and a 480p render can also be taken to 1080p later as a final of the draft. It is the most expensive SKU per second. Reach for it when quality and clip length matter more than cost.seedance-2-fast/seedance-2-mini— the same Seedance 2 grammar at a fraction of the price (480p / 720p, 4–15s). Use them for drafts, batch previews and A/B prompt work, then re-render the keeper onseedance-2orseedance-2-5.
Text-to-video (default) needs only a prompt. Image-to-video
animates a still: pass image_url (an https URL or data: URI) as
the start frame. Every Seedance SKU supports both.
import time, requests
BASE = "https://api.gpuniq.com/v1/llm"
HEADERS = {"X-API-Key": "gpuniq_your_key"}
# Image-to-video with the flagship, 6-second clip
start = requests.post(
f"{BASE}/videos/jobs",
headers=HEADERS,
json={
"model": "seedance-2",
"prompt": "the product slowly rotates on a marble pedestal, soft studio light",
"image_url": "https://example.com/product.jpg", # start frame → image-to-video
"duration": 6, # seedance-2 bills per second → 6 × $0.272 = $1.632
},
).json()["data"]
job_id = start["job_id"]
print("estimated:", start["estimated_cost_usd"]) # 1.632
deadline = time.time() + 300
while time.time() < deadline:
time.sleep(3)
d = requests.get(f"{BASE}/videos/jobs/{job_id}", headers=HEADERS).json()["data"]
if d["status"] == "completed":
print("video:", d["video"]["url"], "cost:", d["cost_usd"])
break
if d["status"] == "failed":
print("failed:", d.get("error"))
break
Seedance shares the standard video job API. The two
-pro SKUs (seedance-1-0-pro, seedance-1-5-pro) do text-to-video and
image-to-video only: duration and image_url are the fields that
matter, aspect_ratio is honoured where the upstream supports it, and
fields specific to other families (audio, mode, resolution tiers,
video_url) are ignored.
The Seedance 2 line (seedance-2, seedance-2-5, seedance-2-fast,
seedance-2-mini) accepts more of the request body. On top of t2v and
i2v it supports:
- First + last frame — pass
image_url(start) together withlast_frame_url(end); the clip interpolates between the two frames. - Reference-to-video — condition the render on reference clips
(
video_url/video_urls), reference images (reference_image_urls) and reference audio tracks (audio_url/audio_urls), in any combination. The counts are per SKU:seedance-2,-fastand-minitake 9 images, 3 clips and 3 audio tracks,seedance-2-5takes 30 images, 10 clips and 10 audio tracks. Audio references need at least one image or video reference alongside them (the API returns a 400 otherwise). A singleimage_urlsent together with any reference input is treated as a subject reference rather than a start frame. First/last-frame and reference-to-video are mutually exclusive — when any reference input is attached,last_frame_urlis dropped. - Resolution choice — via
resolution; the per-second price scales with it (see the pricing tables above). The menu is per SKU:seedance-2renders480p/720p/1080p/4k;seedance-2-5renders480p/720p/1080p(and a 480p render can be finished at 1080p);-fastand-minirender480p/720ponly. A direct request above the menu is a 400. - Generated audio track — set
audio: trueto have the model generate sound for the clip. Default isfalse; we always send the flag explicitly, so an omitted field means a silent clip.
duration applies to every Seedance SKU. seedance-2, -fast and
-mini accept 4–15 seconds; seedance-2-5 accepts 4–30. On the
Seedance 2 line, aspect_ratio additionally accepts auto, 21:9,
4:3 and 3:4 — omit it in i2v mode to follow the input frame's
geometry.
seedance-2 also accepts a seed. Be aware of what it actually buys:
keeping the seed fixed while everything else stays identical makes two
renders markedly more alike than two unseeded ones, but they will not
be identical — the model is not bit-reproducible, so treat the seed as
a similarity control rather than a repeat button. A seeded request is
routed to the one upstream that implements the parameter; if that route
is unavailable the clip is still rendered, without the seed. On
seedance-2-5, -fast and -mini the field is accepted but has no
effect — the upstreams serving those SKUs expose no seed parameter,
so the render is always random.
Reference-file limits: videos 2–15s combined (up to 30s on
seedance-2-5), ≤50MB total; images JPG/PNG/WebP; audio MP3/WAV, ≤15s
combined (30s on seedance-2-5). Video resolution and container are unrestricted —
1080p/4K clips and non-MP4 formats (webm, MKV, …) are automatically
converted to ≤720p MP4 on our side before dispatch, so send your source
footage as-is.
There is no elements parameter. Seedance 2's multi-element /
multimodal reference capability is expressed through the fields above —
image_url / last_frame_url for frames, video_url / video_urls
for reference clips, reference_image_urls for subject/style images
and audio_url / audio_urls for reference audio.
seedance-2-5 — exact request contract
The reference sheet for Seedance 2.5 specifically. Everything below is
what the GPUniq API enforces today — a value outside these ranges is a
400 invalid_request at submit, not a silent clamp.
Endpoint. There is no model-specific route. Seedance 2.5 uses the standard async video-job API:
POST https://api.gpuniq.com/v1/llm/videos/jobs → { job_id, estimated_cost_usd, ... }
GET https://api.gpuniq.com/v1/llm/videos/jobs/{job_id} → poll every 3–5 s
Auth is X-API-Key: gpuniq_... on both calls (a JWT works too). The
kickoff returns in under a second; the job id stays valid across
internal provider fallbacks and lives in our job store for 2 hours,
after which a poll returns job_not_found (404); within that window a
terminal result is replayed to every poll. Renders
usually land in a few minutes; a job that shows no upstream progress
for 30 minutes is failed definitively rather than left polling.
Model slug: seedance-2-5 (exactly this string in the model
field; it is also listed by GET /v1/llm/models/catalog).
Request fields
| Field | Type | Accepted on seedance-2-5 | Default |
|---|---|---|---|
model | string | "seedance-2-5" — required | — |
prompt | string | required, 1–20 000 characters (10 000 with video_task) | — |
duration | int | any integer 4–30 (seconds) | 5 |
resolution | string | 480p, 720p or 1080p — 4k is a 400. At 1080p: no last_frame_url, no video_task, and aspect_ratio: auto needs an image or video input | 720p |
aspect_ratio | string | 16:9, 9:16, 1:1, auto, 21:9, 4:3, 3:4 | 16:9 (auto follows the input frames) |
image_url | string | https URL or data: URI — start frame (i2v) | — |
last_frame_url | string | https URL or data: URI — end frame; requires image_url | — |
video_url | string | one reference clip (https URL or data: URI) | — |
video_urls | array of string | up to 10 reference clips; merged with video_url | — |
reference_image_urls | array of string | up to 30 subject/style images (JPG/PNG/WebP) | — |
audio_url | string | one reference audio track (MP3/WAV) | — |
audio_urls | array of string | up to 10 reference audio tracks; merged with audio_url | — |
audio | bool | true makes the model generate the clip's soundtrack | false |
seed | int | accepted by the schema, has no effect on this SKU | — |
mode, character_orientation | — | ignored (Kling-family fields) | — |
Generation modes. Four, and the frame modes and reference modes are mutually exclusive:
- Text-to-video —
promptonly. - Image-to-video —
prompt+image_url(start frame). - First + last frame —
prompt+image_url+last_frame_url. Order matters:image_urlis always the FIRST frame. - Reference-to-video —
prompt+ any ofreference_image_urls/video_url/video_urls/audio_url/audio_urls. Attaching any reference input switches the job to reference mode:image_urlis then read as one more subject reference andlast_frame_urlis not applied. Audio references require at least one image or video reference alongside them, otherwise the API returns a 400.
Write the prompt as a shot, not as an instruction about the clip.
Seedance reads the prompt to decide what kind of job it was handed. A
prompt that tells the model what to do with the attached clip —
"follow @Video 1 exactly", "extend this shot" — is classified as
video editing, and an edit's length and aspect ratio follow the
source, which makes the duration / aspect_ratio in your request
invalid. The job then fails with a message saying so.
Measured 2026-09-14 on one clip and one request shape, varying only
the prompt: the instructing version failed four times out of four,
while "a calm wide shot, cinematic" on the same reference rendered.
So describe the shot you want — the reference still drives the
performance, motion and look — or, when you really do want to work on
the attached clip, use video_task and omit
duration.
Reference-media limits on this SKU
| Input | Formats | Limit |
|---|---|---|
reference_image_urls | JPG, PNG, WebP | ≤ 30 images (image_url counts as one of them in reference mode) |
video_url + video_urls | any common container (MP4, WebM, MKV, MOV) | ≤ 10 clips, 2–30 s combined, ≤ 50 MB total |
audio_url + audio_urls | MP3, WAV | ≤ 10 tracks, ≤ 30 s combined |
image_url, last_frame_url | JPG, PNG, WebP | one image each |
Where the advertised "50 references" go. The headline for Seedance
2.5 is a sum: 30 images + 10 video clips + 10 audio tracks, and all
three are accepted here as of 2026-09-09. The counts are per SKU, not
per family — the seedance-2, -fast and -mini lines take
9 / 3 / 3, which is their own model's ceiling, and an over-cap
array on either line is a 400 naming the field rather than a silent
trim upstream.
Field names, and how to tell an input arrived. The model's own
passport spells the reference arrays reference_video_urls /
reference_image_urls / reference_audio_urls, and the start frame
first_frame_url. Those spellings are accepted here and folded onto the
names in the table above (so are their camelCase forms, reference_videos,
end_frame_url, source_video_url and duration_seconds). A field name
we don't recognise that looks like an attachment — motion_clip,
image_urls, a typo of a real one — is a 400 naming it, never a
render that quietly ignored it.
Every kickoff answers the question directly in data.inputs:
"inputs": {
"mode": "reference-to-video",
"reference_videos": 1,
"reference_images": 2,
"reference_audio": 0,
"start_frame": false,
"end_frame": false,
"notes": ["`image_url` ships as reference image #1 — address it as @Image1 in the prompt."]
}
"reference_videos": 0 on a job built around a reference clip means the
clip never reached the model — check that on the first call of a new
integration rather than after a batch of renders.
Audio generation. "audio": true turns on the model's native
soundtrack generation for the rendered clip. It is independent of
audio_url / audio_urls, which are input references (pacing /
lip-sync anchors), not output settings. The flag is always sent
explicitly upstream, so omitting it reliably yields a silent clip.
Editing / extension
Supported on this SKU via video_task. Attach
the clip you want to work on as video_url (or video_urls), address it
in the prompt as @Video 1, and set:
video_task | What it does | duration |
|---|---|---|
edit | Rewrites the source in place — relight, remove or replace an element, restyle. Always returns a clip the same length as its source (within ~0.4 s). | must be omitted |
extend | Renders new footage continuing the source. Say the direction in the prompt (Extend @Video 1 forward / backward). | seconds to add, 4–30; omit to let the model choose |
Both modes inherit the source's aspect ratio, so aspect_ratio is
ignored, and both are mutually exclusive with image_url /
last_frame_url — there is no start frame to set when the input is a
whole clip. Scope an edit with plain language (Keep the storefront, lighting and camera move exactly unchanged); an edit aims at continuity,
not pixel-identical preservation of the untouched regions.
Only seedance-2-5. Every other Seedance SKU renders one clip from
one request; video_task on them returns a 400 rather than quietly
generating something new. Edit/extend jobs are also pinned to the one
upstream that implements the mode, so they don't participate in the
usual provider failover.
Cost. Billed exactly like reference-to-video: (source seconds + output seconds) × the reference rate for your resolution. A 10 s source
edited at 480p bills 20 s; the same source extended by 4 s bills 14 s.
Because an edit's output length isn't knowable at submit, the kickoff
estimated_cost_usd assumes the source's measured length and the final
cost_usd is recomputed from the upstream invoice.
The adjacent post-processing surface is Topaz video upscaling
(POST /v1/llm/topaz/video/jobs, see Topaz).
Cost
| Resolution | $/second | $/second in reference-to-video mode |
|---|---|---|
480p | $0.198 | $0.119 |
720p (default) | $0.426 | $0.255 |
1080p | $0.86 | $1.03 — output seconds only |
Billed per second of output, i.e. rate × duration. In
reference-to-video mode at 480p / 720p the upstream meters (input + output) seconds,
so the reference-clip length is added to the billed seconds, capped at
30 s; at 1080p only the output seconds are billed. Because clip length isn't knowable at submit, the kickoff
estimated_cost_usd assumes that 30 s cap and the final cost_usd is
recomputed from the measured length — a ref job with short clips settles
well under its estimate.
Worked examples:
- 5 s, 720p, t2v → 5 × $0.426 = $2.13
- 30 s, 720p, t2v → 30 × $0.426 = $12.78
- 10 s, 480p, t2v → 10 × $0.198 = $1.98
- 8 s, 720p, ref-to-video with a 6 s clip → estimated (8 + 30) × $0.255 = $9.69, billed (8 + 6) × $0.255 = $3.57
- 30 s, 1080p, t2v → 30 × $0.86 = $25.80
- 20 s, 1080p, ref-to-video with a 14 s clip → 20 × $1.03 = $20.60
- 30 s, 1080p via a draft → 480p draft 30 × $0.198 = $5.94, then the final 30 × $0.86 = $25.80 — $31.74 for both jobs
Example request
# 1. Kick off — 8-second 720p reference-to-video with generated audio
curl -X POST https://api.gpuniq.com/v1/llm/videos/jobs \
-H "X-API-Key: gpuniq_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2-5",
"prompt": "the model walks toward camera through neon-lit rain, slow dolly-in, cinematic",
"duration": 8,
"resolution": "720p",
"aspect_ratio": "9:16",
"reference_image_urls": [
"https://example.com/subject-front.jpg",
"https://example.com/subject-side.jpg"
],
"video_urls": ["https://example.com/motion-ref.mp4"],
"audio": true
}'
# → {"exception":0,"data":{"job_id":"vid_e93e98c7ca5e4982876b",
# "status":"pending","estimated_cost_usd":9.69,
# "inputs":{"mode":"reference-to-video",
# "reference_videos":1,"reference_images":2,
# "reference_audio":0,...},...}}
# 2. Poll every 3–5 s until status is completed or failed
curl https://api.gpuniq.com/v1/llm/videos/jobs/vid_e93e98c7ca5e4982876b \
-H "X-API-Key: gpuniq_your_key"
# → {"exception":0,"data":{"status":"completed",
# "video":{"url":"https://.../out.mp4"},
# "cost_usd":3.57,"balance_usd":41.02}}
The simplest possible call is just the slug and a prompt — everything else has a default:
curl -X POST https://api.gpuniq.com/v1/llm/videos/jobs \
-H "X-API-Key: gpuniq_your_key" \
-H "Content-Type: application/json" \
-d '{"model":"seedance-2-5","prompt":"a paper boat drifting down a rain gutter"}'
Seedance 2.5 with and without Draft Mode
Draft Mode is ByteDance's two-step way to 1080p: render the shot at 480p,
look at it, and then render the same shot at 1080p. On
seedance-2-5 there is no field that switches it on or off. The
resolution you send decides:
| You send | Draft Mode | You get | List price |
|---|---|---|---|
"resolution": "480p" | on, automatically | a 480p clip, plus a draft block: the same shot can be rendered at 1080p for 7 days | $0.198/s |
"resolution": "720p" (default) | off | a 720p clip | $0.426/s |
"resolution": "1080p" | off | a 1080p clip in one job | $0.86/s |
POST /v1/llm/videos/jobs/{job_id}/final on a 480p draft | — | the draft's shot at 1080p, as a new job | $0.86/s |
Which one to use. Draft Mode pays off when you iterate on a prompt: try it at 480p a few times and pay the 1080p rate only for the take you keep. Four 5-second tries and one keeper cost 4 × $0.99 + $4.30 = $8.26 through drafts, against 4 × $4.30 = $17.20 as four direct 1080p renders. When you already know the shot, a direct 1080p render is one job and $0.99 cheaper per 5 s than draft + final.
Without Draft Mode: 1080p in one job
Send "resolution": "1080p". Nothing else changes: same endpoint, same
polling, same fields as any other seedance-2-5 job.
# 1. Kick off a 5-second 1080p clip — no draft involved
curl -X POST https://api.gpuniq.com/v1/llm/videos/jobs \
-H "X-API-Key: gpuniq_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2-5",
"prompt": "a paper boat drifting down a rain gutter, macro shot, evening light",
"duration": 5,
"resolution": "1080p",
"aspect_ratio": "16:9"
}'
# → {"exception":0,"data":{"job_id":"vid_5d2e81c0b7a94f6e8c13",
# "status":"pending","estimated_cost_usd":4.3,...}}
# 2. Poll every 3–5 s until status is completed or failed
curl https://api.gpuniq.com/v1/llm/videos/jobs/vid_5d2e81c0b7a94f6e8c13 \
-H "X-API-Key: gpuniq_your_key"
# → {"exception":0,"data":{"status":"completed",
# "video":{"url":"https://.../out.mp4"},
# "cost_usd":4.3,...}} ← no "draft" block
import time, requests
BASE = "https://api.gpuniq.com/v1/llm"
HEADERS = {"X-API-Key": "gpuniq_your_key"}
start = requests.post(f"{BASE}/videos/jobs", headers=HEADERS, json={
"model": "seedance-2-5",
"prompt": "a paper boat drifting down a rain gutter, macro shot, evening light",
"duration": 5,
"resolution": "1080p", # direct 1080p — no draft
"aspect_ratio": "16:9",
}).json()["data"]
deadline = time.time() + 900
while time.time() < deadline:
time.sleep(5)
d = requests.get(f"{BASE}/videos/jobs/{start['job_id']}", headers=HEADERS).json()["data"]
if d["status"] in ("completed", "failed"):
break
print(d["status"], d.get("video", {}).get("url"), d.get("cost_usd"))
What is different at 1080p:
- No
last_frame_urland novideo_task(edit / extend run at 480p or 720p). A start frame (image_url) and reference images, videos and audio all work. aspect_ratio: "auto"needs an image or a video to take the frame from; without one, name a ratio (16:9,9:16,1:1,4:3,3:4,21:9). Each of these is a 400 at kickoff, before anything is billed.- With reference video the rate is $1.03/s and only the output seconds are billed; the reference clip adds nothing at this tier.
- The tier renders on a single upstream, so there is no provider
failover. If your personal price falls below what that upstream
charges, the job is refused with
no_provider_available— contact support.
With Draft Mode: 480p draft, then the 1080p final
1. Render the 480p draft. An ordinary job with "model": "seedance-2-5" and "resolution": "480p". There is no draft flag to
set: a fresh 480p render on this SKU is kept as a draft automatically.
It is billed like any 480p clip ($0.198/s).
curl -X POST https://api.gpuniq.com/v1/llm/videos/jobs \
-H "X-API-Key: gpuniq_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2-5",
"prompt": "a paper boat drifting down a rain gutter, macro shot, evening light",
"duration": 30,
"resolution": "480p",
"aspect_ratio": "16:9"
}'
# → {"exception":0,"data":{"job_id":"vid_3f1c0b5e2a7d4c8e9f01","status":"pending",...}}
2. Read data.draft in the completed poll. A job that was kept as
a draft carries the offer for its final:
// GET /v1/llm/videos/jobs/vid_3f1c0b5e2a7d4c8e9f01 — a completed 480p draft
{
"status": "completed",
"job_id": "vid_3f1c0b5e2a7d4c8e9f01",
"model": "seedance-2-5",
"video": { "url": "https://.../draft.mp4", "stored_url": "https://api.gpuniq.com/v1/llm/media/....mp4" },
"cost_usd": 5.94,
"draft": {
"final_available": true,
"final_resolution": "1080p",
"final_estimated_cost_usd": 25.8,
"expires_at": "2026-10-08T08:12:40+00:00",
"final_endpoint": "/v1/llm/videos/jobs/vid_3f1c0b5e2a7d4c8e9f01/final"
}
}
final_estimated_cost_usd is already your price for the final,
after your personal discount.
3. Render the final. POST to final_endpoint with no body:
curl -X POST https://api.gpuniq.com/v1/llm/videos/jobs/vid_3f1c0b5e2a7d4c8e9f01/final \
-H "X-API-Key: gpuniq_your_key"
# → {"exception":0,"data":{"job_id":"vid_9a07d2c4e6b84f13a5c7","status":"pending",
# "model":"seedance-2-5","resolution":"1080p",
# "final_of":"vid_3f1c0b5e2a7d4c8e9f01","estimated_cost_usd":25.8}}
The answer is a new job_id. Poll it at
GET /v1/llm/videos/jobs/{job_id} like any other video job. It is
charged on delivery at the 1080p rate, $0.86/s at list price. In the web
chat's video panel the same action is the 1080p · $X button next to
Download.
The whole flow in Python:
import time, requests
HOST = "https://api.gpuniq.com"
BASE = f"{HOST}/v1/llm"
HEADERS = {"X-API-Key": "gpuniq_your_key"}
def wait(job_id, budget_s=900):
deadline = time.time() + budget_s
while time.time() < deadline:
time.sleep(5)
d = requests.get(f"{BASE}/videos/jobs/{job_id}", headers=HEADERS).json()["data"]
if d["status"] in ("completed", "failed"):
return d
raise TimeoutError(job_id)
# 1. 480p draft — Draft Mode is automatic at 480p
job = requests.post(f"{BASE}/videos/jobs", headers=HEADERS, json={
"model": "seedance-2-5",
"prompt": "a paper boat drifting down a rain gutter, macro shot, evening light",
"duration": 5,
"resolution": "480p",
"aspect_ratio": "16:9",
}).json()["data"]
draft = wait(job["job_id"])
offer = draft.get("draft") or {}
print("draft:", draft.get("video", {}).get("url"),
"| 1080p final would cost", offer.get("final_estimated_cost_usd"))
# 2. Keep this take? Render the same shot at 1080p — no body
if offer.get("final_available"):
final_job = requests.post(f"{HOST}{offer['final_endpoint']}", headers=HEADERS).json()["data"]
final = wait(final_job["job_id"])
print("1080p:", final.get("video", {}).get("url"), "cost:", final.get("cost_usd"))
What the final keeps. Prompt, duration, aspect ratio, container, generated audio and every reference input come from the draft, which is why the call takes no body and nothing in it can be changed. Composition, subject, motion and timing match the draft. Fine detail can differ a little: the final is a new render at the higher resolution, not an upscale of the draft file.
Rules
- The final can be requested for 7 days after the draft
(
expires_at). - Drafts exist only for
seedance-2-5at480p. A 720p or 1080p job, an edit/extend job (video_task) and every other model have no draft. - No
draftblock in a completed 480p job means the clip was rendered on a route that cannot keep a draft (for example after an internal fallback). That clip has no 1080p final. Render a new 480p job, or render the shot at 1080p directly. - Only the account that rendered the draft can finalise it.
- The final runs on the one upstream that holds the draft, so unlike a normal job it has no provider failover.
Errors on /final. As everywhere on the native API, they arrive as
an envelope with a non-zero exception, so branch on
body["exception"], not on the HTTP status:
error_code | exception | Meaning |
|---|---|---|
invalid_request | 400 | No draft for this job: another model or resolution, a render kept without a draft, someone else's job, or older than 7 days. |
insufficient_balance | 402 | Top up and call /final again; the draft stays valid until expires_at. |
no_provider_available | 400 | The final isn't available at your current price. Contact support. |
provider_unavailable | 503 | Temporary. Retry with backoff. |
Cost of a 1080p clip, at list price — through a draft (two jobs) or directly (one job):
| Duration | 480p draft ($0.198/s) | 1080p final ($0.86/s) | Total | Direct 1080p |
|---|---|---|---|---|
| 5 s | $0.99 | $4.30 | $5.29 | $4.30 |
| 10 s | $1.98 | $8.60 | $10.58 | $8.60 |
| 30 s | $5.94 | $25.80 | $31.74 | $25.80 |
A draft rendered in reference-to-video mode bills at the reference rate (see above); the final always bills its output seconds at the 1080p rate. Personal discounts apply to each job separately; the final uses your 1080p discount when you have one.