🎁 New Sign up free, 10 calls on us. Up to $1, no card needed.

Video Generation

POST /v1/videos · GET /v1/videos/{id} · DELETE /v1/videos/{id}

Generate short videos from a text prompt, optionally steered by first/last reference frames. This is an asynchronous job API in the industry-standard create + poll shape: POST /v1/videos creates a task that returns in ~2 seconds, you poll it until it completes, and a Prefer: wait header turns fast generations into a single synchronous call. Seedance models, one API key, the same billing as every other endpoint.

The video API is in limited preview (invited testing). Models appear in the catalog and pricing, but creating tasks requires your workspace to be allowlisted — contact us to enable it for your workspace.

Endpoints & task lifecycle

One task resource, four operations. A task moves queuedin_progress → one of the terminal states completed, failed or cancelled. The create response carries ready-made status_url / cancel_url links, so you never assemble URLs by hand.

EndpointDescription
POST /v1/videosCreate a generation task. Returns in ~2 s with the task object (status queued) plus polling links.
GET /v1/videos/{id}Poll the task. On completed you get data[0].url, usage.completion_tokens and the parameters that actually took effect; on failed a structured error with the provider’s code and message.
DELETE /v1/videos/{id}Cancel the task. Only queued tasks can be cancelled; cancelled tasks are never billed.
GET /v1/videos/modelsList the video models available to your key, with per-model resolution/duration constraints and prices.

Synchronous sugar — Prefer: wait

Add a Prefer: wait or Prefer: wait=N header (N clamped to 1..60 seconds) to the create call and the gateway holds the request open. If the task finishes inside the window you get the terminal object straight back — video URL and usage included, no polling needed. If it does not finish in time, the call degrades gracefully: you get the current status object (no error, the task keeps running) and continue with normal polling.

Request Body

ParameterTypeDescription
model*stringThe video model to use (e.g. seedance-1-5-pro-251215). List all available models via GET /v1/videos/models.
prompt*stringText description of the desired video.
imagestring | string[]Image-to-video input: one image = first frame, two images = first + last frame. Each entry is a data URI or an https URL.
resolutionstringOutput resolution: 480p, 720p, 1080p or 4k. The supported set varies per model — see the model table.
ratiostringAspect ratio: 16:9, 4:3, 1:1, 3:4, 9:16, 21:9 or adaptive.
durationintegerClip length in seconds (per-model range within 2..15). Some models also accept -1 to let the model choose a length.
seedintegerOptional seed for reproducible output (where supported).
watermarkbooleanWhether to render the provider watermark.
generate_audiobooleanGenerate an audio track (default true). On models with split rates this changes the price — see the model table.

Available models

Model availability depends on your deployment. Fetch the live, compliance-aware list — including each model’s resolution/duration constraints and prices — via GET /v1/videos/models. The current lineup:

ModelResolutionsDurationPriceNotes
dreamina-seedance-2-0-260128 480p · 720p · 1080p · 4k 4–15 s 480p/720p $7.0 · 1080p $7.7 · 4k $4.0 2.0 flagship; up to 4K
dreamina-seedance-2-0-fast-260128 480p · 720p 4–15 s $5.6 Faster 2.0 variant
dreamina-seedance-2-0-mini-260615 480p · 720p 4–15 s $3.5 Smallest 2.0 variant
seedance-1-5-pro-251215 480p · 720p · 1080p 4–12 s $2.4 audio · $1.2 muted Rate depends on generate_audio
seedance-1-0-pro-fast-251015 480p · 720p · 1080p 2–12 s $1.0 Cheapest

Price: USD per 1M video tokens. seedance-1-5-pro-251215 bills $2.4 with the audio track on (generate_audio: true, the default) and $1.2 with it off; the other models bill one rate per resolution tier.

Video tokens & pricing

Video is billed by video tokens, derived from the actual output: tokens ≈ width × height × 24 fps × seconds / 1024. Rates are per 1M video tokens and follow the parameters that actually took effect (echoed back in the completed task) — so ratio: "adaptive" and duration: -1 are billed by what the model really produced.

Intuition anchors on seedance-1-5-pro-251215 with audio: a 480p / 16:9 / 4 s clip ≈ 40.6K video tokens ≈ $0.10; a 720p / 5 s clip ≈ 108.9K tokens ≈ $0.26.

Billing

Billed only on success: the charge lands exactly once, when the task first reaches completed, priced by the returned video-token usage. Failed tasks (including content-moderation rejections) and cancelled tasks are never billed — the provider’s error is passed through verbatim so you can see why.

!

The returned data[0].url is a pre-signed vendor URL that expires in ~24 hours — download promptly and re-host the file on your own storage if you need it long-term.

Example — curl, one shot (Prefer: wait)

curl https://synthorai.io/v1/videos \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=60" \
  -d '{
    "model": "seedance-1-0-pro-fast-251015",
    "prompt": "A corgi puppy chasing a butterfly across a meadow, cinematic",
    "resolution": "480p",
    "ratio": "16:9",
    "duration": 4
  }'

# Finished inside the wait window → the terminal object comes straight back:
{
  "id": "vid_6091d8d1af805818b1470488",
  "object": "video",
  "model": "seedance-1-0-pro-fast-251015",
  "status": "completed",
  "data": [{ "url": "https://…tos….mp4?…" }],
  "usage": { "completion_tokens": 40594, "total_tokens": 40594 },
  "resolution": "480p", "ratio": "16:9", "duration": 4, "fps": 24,
  "seed": 34142
}
# Not done in time → the current status object is returned instead (no error,
# the task keeps running) — continue polling status_url as usual.

Example — curl, create + poll

# 1) Create — returns in ~2 s with a queued task object
curl https://synthorai.io/v1/videos \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-1-5-pro-251215",
    "prompt": "A corgi puppy chasing a butterfly across a meadow, cinematic",
    "resolution": "720p",
    "duration": 5,
    "generate_audio": true
  }'

{
  "id": "vid_6091d8d1af805818b1470488",
  "object": "video",
  "model": "seedance-1-5-pro-251215",
  "status": "queued",
  "created_at": 1784619862,
  "status_url": "https://synthorai.io/v1/videos/vid_6091d8d1af805818b1470488",
  "cancel_url": "https://synthorai.io/v1/videos/vid_6091d8d1af805818b1470488"
}

# 2) Poll until status is terminal (completed | failed | cancelled)
curl https://synthorai.io/v1/videos/vid_6091d8d1af805818b1470488 \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY"

{
  "id": "vid_6091d8d1af805818b1470488",
  "object": "video",
  "model": "seedance-1-5-pro-251215",
  "status": "completed",
  "data": [{ "url": "https://…tos….mp4?…" }],
  "usage": { "completion_tokens": 108900, "total_tokens": 108900 },
  "resolution": "720p", "ratio": "16:9", "duration": 5, "fps": 24,
  "seed": 34142, "generate_audio": true
}

# 3) Download the video — the URL is pre-signed (no auth header) and expires
#    in ~24 h: fetch promptly and re-host if you need it long-term.
curl -o out.mp4 "https://…tos….mp4?…"

Example — Python (requests)

import time
import requests

BASE = "https://synthorai.io/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}

task = requests.post(f"{BASE}/videos", headers=HEADERS, json={
    "model": "seedance-1-5-pro-251215",
    "prompt": "A corgi puppy chasing a butterfly across a meadow, cinematic",
    "resolution": "720p",
    "duration": 5,
}).json()

while task["status"] not in ("completed", "failed", "cancelled"):
    time.sleep(5)
    task = requests.get(task["status_url"], headers=HEADERS).json()

if task["status"] == "completed":
    url = task["data"][0]["url"]  # pre-signed, valid ~24 h — re-host promptly
    with open("out.mp4", "wb") as f:
        f.write(requests.get(url).content)
else:
    print(task["error"])  # provider code + message, passed through verbatim

Example — Node (fetch)

import fs from "node:fs";

const BASE = "https://synthorai.io/v1";
const HEADERS = { Authorization: `Bearer ${process.env.SYNTHORAI_API_KEY}` };

let task = await (await fetch(`${BASE}/videos`, {
  method: "POST",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "seedance-1-5-pro-251215",
    prompt: "A corgi puppy chasing a butterfly across a meadow, cinematic",
    resolution: "720p",
    duration: 5,
  }),
})).json();

while (!["completed", "failed", "cancelled"].includes(task.status)) {
  await new Promise((r) => setTimeout(r, 5000));
  task = await (await fetch(task.status_url, { headers: HEADERS })).json();
}

if (task.status === "completed") {
  const url = task.data[0].url; // pre-signed, valid ~24 h — re-host promptly
  fs.writeFileSync("out.mp4", Buffer.from(await (await fetch(url)).arrayBuffer()));
} else {
  console.error(task.error); // provider code + message, passed through verbatim
}

Idempotency

Pass an X-Idempotency-Key header on the create call to make retries safe: a repeat request with the same key returns 409 instead of creating (and billing) a second task. Video generation is slow and retry-prone, so this prevents double charges.