LLM & Media APIVideo generation

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.

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

FieldTypeRequiredNotes
modelstringyesSlug from the catalog.
promptstringyesUp to 10 000 characters; up to 20 000 on seedance-2 and seedance-2-5 (10 000 for video_task edit / extend jobs).
durationintnoSeconds. 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_ratiostringno16: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_urlstringnohttps URL or data URI — enables image-to-video (start frame).
last_frame_urlstringnohttps 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_urlstringnohttps 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_urlsarraynoReference 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_urlsarraynoSeedance 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_urlstringnoSeedance 2 line only — reference audio track (MP3/WAV). Requires at least one image or video reference in the same request.
audio_urlsarraynoSeedance 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.
resolutionstringnoSeedance 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.
audioboolnoDefault 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).
modestringnostandard / pro (default) / master for Kling 2.1; turbo for 2.5 Turbo Pro.
character_orientationstringnoKling 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.
seedintnoseedance-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_formatstringnoContainer 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_qualityintnoCurrently 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.

ModelLegal durationDefault
veo-3-1-lite / veo-3-1-fast / veo-3-1-quality4, 6 or 8 — nothing else8
veo-3-1-fast / -lite with reference_image_urls8 only8
kling-2-1, kling-2-5-turbo-pro, kling-2-65 or 105
kling-3-05 or 10 (multi-shot to 15)5
kling-o3-videoany integer 3–155
kling-2-6-motion-control, kling-3-0-motion-control5 or 105
kling-avatar-pro, kling-avatar-standardany integer 1–155
seedance-2, seedance-2-fast, seedance-2-miniany integer 4–155
seedance-2-5any integer 4–305
seedance-1-0-pro, seedance-1-5-pro55
sora-2-video, sora-2-pro-video1010
sora-2-official, sora-2-pro-official88

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:

FormLooks likeWhere it works
Public HTTPS URLhttps://cdn.example.com/ref.jpgevery 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:

  1. A direct link to the file, not to a viewer page. The response must be the bytes themselves.
  2. No authentication, no redirect chain to a login page. The fetcher is anonymous and has no cookies.
  3. 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.
  4. A file extension that matches the content where possible — some upstreams sniff the URL path before they sniff the body.

Size and format limits:

FieldFormatsLimits
image_url, last_frame_urlJPG, PNG, WebPone image each
reference_image_urlsJPG, 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_urlsany 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_urlsMP3, 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.

Modelimage_urllast_frame_urlreference_image_urlsvideo_urlaudio_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:

FieldWhat it is
video.url / image.b64_jsonthe original delivery — live immediately, unchanged
video.stored_url / image.urla GPUniq-hosted copy at https://api.gpuniq.com/v1/llm/media/{token}
video.stored_readyfalse 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_dayshow 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.

FamilySlugHeadline / videoNotes
OpenAI Sora 2sora-2-video$0.600Sora 2, default 10s
OpenAI Sora 2 Prosora-2-pro-video$1.000Premium quality, 10s
Sora 2 Officialsora-2-official$0.4808s, official API
Sora 2 Pro Officialsora-2-pro-official$0.5608s 1080p, official API
Google Veo 3.1 Liteveo-3-1-litefrom $0.30720p / 1080p / 4K, 4–8s; i2v, first/last frame, reference-to-video
Google Veo 3.1 Fastveo-3-1-fastfrom $0.60720p / 1080p / 4K, 4–8s; i2v, first/last frame, reference-to-video
Google Veo 3.1 Qualityveo-3-1-qualityfrom $2.50Flagship; 720p / 1080p / 4K, 4–8s; i2v, first/last frame
Kling 2.1 Prokling-2-1$0.405Standard / Pro / Master tiers, 5s or 10s, i2v
Kling 2.5 Turbo Prokling-2-5-turbo-pro$0.3155s or 10s, t2v / i2v
Kling 2.6kling-2-6$0.315Optional audio, 5s or 10s, t2v / i2v
Kling 3.0kling-3-0$0.504720p / 1080p / 4K, audio, multi-shot to 15s
Kling O3 (Video)kling-o3-videofrom $0.076 / s720p / 1080p / 4K, native audio, 3–15s; t2v / i2v, first+last frame, reference images (≤4)
Kling 2.6 Motion Controlkling-2-6-motion-control$0.504720p / 1080p video-to-video
Kling 3.0 Motion Controlkling-3-0-motion-control$0.756720p / 1080p video-to-video
Kling AI Avatar Prokling-avatar-pro$1.0351080p lip-sync, up to 15s
Kling AI Avatar Standardkling-avatar-standard$0.506720p lip-sync, up to 15s
Hailuo 02hailuo-02$0.200768p, 6s default
Hailuo 2.3hailuo-2-3$0.350768p 6s
Seedance 1.0 Proseedance-1-0-pro$0.210ByteDance 720p, 5s, t2v + i2v (per video)
Seedance 1.5 Proseedance-1-5-pro$0.160ByteDance 720p, 5s, t2v + i2v (per video)
Seedance 2seedance-2$0.122–$1.40 / sByteDance 480p–4k, billed per second, rate depends on resolution (720p: $0.272/s, 5s ≈ $1.36)
Seedance 2.5seedance-2-5$0.198–$0.86 / sByteDance 480p / 720p / 1080p, 4–30s, billed per second (720p: $0.426/s, 5s ≈ $2.13); first/last frame, reference images + video + audio
Seedance 2 Fastseedance-2-fast$0.13–$0.27 / sFaster, cheaper Seedance 2 line — 480p / 720p, 4–15s, billed per second
Seedance 2 Miniseedance-2-mini$0.08–$0.17 / sSmallest Seedance 2 line — 480p / 720p, 4–15s, billed per second
Alibaba Wan 2.2 Fastwan-2-2-fast$0.120720p fast tier
Alibaba Wan 2.5wan-2-5$0.600720p 5s
Alibaba Wan 2.6wan-2-6$0.800720p 5s flagship
Wan Animatewan-animate$0.150720p animation
Happy Horsehappy-horse$0.160720p
Grok Imagine Videogrok-imagine-video$0.300xAI video, 6s
Grok Imagine Video 1.5grok-imagine-video-1-5-previewfrom $0.08 / sxAI, billed per second, rate by resolution
Runway Gen-4.5runway-gen-4-5$0.750Runway 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.

ResolutionLiteFastQuality
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_urls on veo-3-1-fast / veo-3-1-lite (8-second clips only; not available on Quality). Mutually exclusive with last_frame_url.
  • aspect_ratio: 16:9 (default), 9:16, or auto (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.

ResolutionAudio offAudio 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_ratio is 16:9 (default), 9:16 or 1:1.
  • Image-to-video — image_url (start frame), optionally with last_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_url may ride along as start / end anchors.
  • No reference video. video_url / video_urls are rejected with a 400 on this model: the upstream accepts a clip at submit and then fails the render. For video-to-video use kling-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.

SlugModelResolutionDuration (default)ModesBilling
seedance-1-0-proSeedance 1.0 Pro720p5st2v, i2vFlat $0.210 / video
seedance-1-5-proSeedance 1.5 Pro720p5st2v, i2vFlat $0.160 / video
seedance-2Seedance 2480p / 720p / 1080p / 4k4–15s (5)t2v, i2v, first/last-frame, ref-to-video (image + video + audio refs)Per second, by resolution — see below
seedance-2-5Seedance 2.5480p / 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-fastSeedance 2 Fast480p / 720p4–15s (5)t2v, i2v, first/last-frame, ref-to-videoPer second, by resolution
seedance-2-miniSeedance 2 Mini480p / 720p4–15s (5)t2v, i2v, first/last-frame, ref-to-videoPer 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:

Resolutionfast $/sfast + ref videomini $/smini + 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 on seedance-2 or seedance-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 with last_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, -fast and -mini take 9 images, 3 clips and 3 audio tracks, seedance-2-5 takes 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 single image_url sent 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_url is dropped.
  • Resolution choice — via resolution; the per-second price scales with it (see the pricing tables above). The menu is per SKU: seedance-2 renders 480p / 720p / 1080p / 4k; seedance-2-5 renders 480p / 720p / 1080p (and a 480p render can be finished at 1080p); -fast and -mini render 480p / 720p only. A direct request above the menu is a 400.
  • Generated audio track — set audio: true to have the model generate sound for the clip. Default is false; 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

FieldTypeAccepted on seedance-2-5Default
modelstring"seedance-2-5" — required—
promptstringrequired, 1–20 000 characters (10 000 with video_task)—
durationintany integer 4–30 (seconds)5
resolutionstring480p, 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 input720p
aspect_ratiostring16:9, 9:16, 1:1, auto, 21:9, 4:3, 3:416:9 (auto follows the input frames)
image_urlstringhttps URL or data: URI — start frame (i2v)—
last_frame_urlstringhttps URL or data: URI — end frame; requires image_url—
video_urlstringone reference clip (https URL or data: URI)—
video_urlsarray of stringup to 10 reference clips; merged with video_url—
reference_image_urlsarray of stringup to 30 subject/style images (JPG/PNG/WebP)—
audio_urlstringone reference audio track (MP3/WAV)—
audio_urlsarray of stringup to 10 reference audio tracks; merged with audio_url—
audiobooltrue makes the model generate the clip's soundtrackfalse
seedintaccepted 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:

  1. Text-to-video — prompt only.
  2. Image-to-video — prompt + image_url (start frame).
  3. First + last frame — prompt + image_url + last_frame_url. Order matters: image_url is always the FIRST frame.
  4. Reference-to-video — prompt + any of reference_image_urls / video_url / video_urls / audio_url / audio_urls. Attaching any reference input switches the job to reference mode: image_url is then read as one more subject reference and last_frame_url is 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

InputFormatsLimit
reference_image_urlsJPG, PNG, WebP≤ 30 images (image_url counts as one of them in reference mode)
video_url + video_urlsany common container (MP4, WebM, MKV, MOV)≤ 10 clips, 2–30 s combined, ≤ 50 MB total
audio_url + audio_urlsMP3, WAV≤ 10 tracks, ≤ 30 s combined
image_url, last_frame_urlJPG, PNG, WebPone 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_taskWhat it doesduration
editRewrites 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
extendRenders 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 sendDraft ModeYou getList price
"resolution": "480p"on, automaticallya 480p clip, plus a draft block: the same shot can be rendered at 1080p for 7 days$0.198/s
"resolution": "720p" (default)offa 720p clip$0.426/s
"resolution": "1080p"offa 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

What is different at 1080p:

  • No last_frame_url and no video_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-5 at 480p. A 720p or 1080p job, an edit/extend job (video_task) and every other model have no draft.
  • No draft block 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_codeexceptionMeaning
invalid_request400No draft for this job: another model or resolution, a render kept without a draft, someone else's job, or older than 7 days.
insufficient_balance402Top up and call /final again; the draft stays valid until expires_at.
no_provider_available400The final isn't available at your current price. Contact support.
provider_unavailable503Temporary. Retry with backoff.

Cost of a 1080p clip, at list price — through a draft (two jobs) or directly (one job):

Duration480p draft ($0.198/s)1080p final ($0.86/s)TotalDirect 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.