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 only 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 only when a poll returns a video.url. 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. |
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 / -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 | Encoder quality of the delivered file, 20–99. The upstream default is 95, already the top of the visually lossless range — lower values shrink the file, higher ones cannot add detail the model did not render. Honoured on the same models as mov / webm above (the Seedance 2 line and Kling 3.0 / O3 / 2.6 / 2.5 Turbo Pro); a 400 elsewhere. No route exposes a bitrate or a codec: bitrate, bitrate_mode and codec are refused with a hint rather than ignored. If a clip still 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
},
"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} |
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. 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.
| 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.426 / s | ByteDance 480p / 720p only, 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 only | 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 (no 1080p / 4k tier upstream):
| Resolution | $/second | $/second with reference video(s) |
|---|---|---|
480p | $0.198 | $0.119 |
720p (default) | $0.426 | $0.255 |
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, but it renders 480p / 720p only and is the most expensive 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, whileseedance-2-5,-fastand-minirender480p/720ponly and return a 400 for anything above. - 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 60
minutes, after which a poll returns job_not_found (404). 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–10 000 characters | — |
duration | int | any integer 4–30 (seconds) | 5 |
resolution | string | 480p or 720p only — 1080p / 4k are a 400 on this SKU | 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 |
Billed per second of output, i.e. rate × duration. In
reference-to-video mode the upstream meters (input + output) seconds,
so the reference-clip length is added to the billed seconds, capped at
30 s. 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
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"}'