# Generate a video Requests for text to video, first frames, first and last frames, reference images and reference media. Every case calls the same endpoint, `POST /v1/video/generations`; only the `content` items differ. The task, its reservation and its idempotency record are accepted together, and the API answers with `202` and the task ID while generation runs in the background. All parameters are on [Create a video task](./api-docs-en-api-video-tasks-create.md). ## Text to video Put a single `text` item in `content` that describes the scene, the action and the camera. ```bash IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)} printf '%s\n' "Save IDEMPOTENCY_KEY for retries: ${IDEMPOTENCY_KEY}" >&2 curl --request POST "${CHENVOYA_BASE_URL}/v1/video/generations" \ --header "Authorization: Bearer ${CHENVOYA_API_KEY}" \ --header "Idempotency-Key: ${IDEMPOTENCY_KEY}" \ --header 'Content-Type: application/json' \ --data '{ "model": "dreamina-seedance-2-0-260128", "content": [ { "type": "text", "text": "A paper boat crossing a moonlit lake." } ] }' ``` ```python import urllib.request import urllib.parse import json import os import sys import re import uuid key = os.environ["CHENVOYA_API_KEY"].strip() if not key or re.search(r"[\x00-\x1f\x7f-\x9f]", key): sys.exit("Set CHENVOYA_API_KEY to a non-empty value without control characters") base = os.environ["CHENVOYA_BASE_URL"].rstrip("/") headers = {"Authorization": "Bearer " + key} idempotency = os.environ.get("IDEMPOTENCY_KEY") or str(uuid.uuid4()) print("Save IDEMPOTENCY_KEY for retries: " + idempotency, file=sys.stderr) headers["Idempotency-Key"] = idempotency path = "/v1/video/generations" body_text = "{\"model\":\"dreamina-seedance-2-0-260128\",\"content\":[{\"type\":\"text\",\"text\":\"A paper boat crossing a moonlit lake.\"}]}" headers["Content-Type"] = "application/json" body = json.dumps(json.loads(body_text)).encode("utf-8") request = urllib.request.Request(base + path, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(request) as response: print(response.read().decode("utf-8")) except urllib.request.HTTPError as error: print(error.read().decode("utf-8")) sys.exit(1) ``` ```javascript // Needs Node.js 20 or later; save it as a .mjs file and run it. const key = process.env.CHENVOYA_API_KEY?.trim(); if (!key || /[\x00-\x1f\x7f-\x9f]/.test(key)) { console.error("Set CHENVOYA_API_KEY to a non-empty value without control characters"); process.exit(1); } const base = process.env.CHENVOYA_BASE_URL; if (!base) throw new Error("Set CHENVOYA_BASE_URL"); const headers = {Authorization: `Bearer ${key}`}; const idempotency = process.env.IDEMPOTENCY_KEY || crypto.randomUUID(); console.error("Save IDEMPOTENCY_KEY for retries: " + idempotency); headers["Idempotency-Key"] = idempotency; const path = "/v1/video/generations"; let bodyText = "{\"model\":\"dreamina-seedance-2-0-260128\",\"content\":[{\"type\":\"text\",\"text\":\"A paper boat crossing a moonlit lake.\"}]}"; headers["Content-Type"] = "application/json"; const body = JSON.stringify(JSON.parse(bodyText)); const response = await fetch(base.replace(/\/$/, "") + path, {method: "POST", headers, body: body}); console.log(await response.text()); if (!response.ok) process.exitCode = 1; ``` ```go package main import ( "crypto/rand" "fmt" "io" "net/http" "os" "regexp" "strings" ) func main() { key := strings.TrimSpace(os.Getenv("CHENVOYA_API_KEY")) if key == "" || regexp.MustCompile(`[\x00-\x1f\x7f-\x9f]`).MatchString(key) { fail("Set CHENVOYA_API_KEY to a non-empty value without control characters") } base := strings.TrimRight(os.Getenv("CHENVOYA_BASE_URL"), "/") if base == "" { fail("Set CHENVOYA_BASE_URL") } path := "/v1/video/generations" bodyText := "{\"model\":\"dreamina-seedance-2-0-260128\",\"content\":[{\"type\":\"text\",\"text\":\"A paper boat crossing a moonlit lake.\"}]}" request, err := http.NewRequest("POST", base+path, strings.NewReader(bodyText)) if err != nil { fail(err.Error()) } request.Header.Set("Authorization", "Bearer "+key) request.Header.Set("Content-Type", "application/json") idempotency := os.Getenv("IDEMPOTENCY_KEY") if idempotency == "" { idempotency = newOperationKey() } fmt.Fprintln(os.Stderr, "Save IDEMPOTENCY_KEY for retries: "+idempotency) request.Header.Set("Idempotency-Key", idempotency) response, err := http.DefaultClient.Do(request) if err != nil { fail(err.Error()) } text, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { fail(err.Error()) } fmt.Println(string(text)) if response.StatusCode < 200 || response.StatusCode > 299 { os.Exit(1) } } func fail(message string) { fmt.Fprintln(os.Stderr, message) os.Exit(1) } // newOperationKey returns a random UUID; save it to retry with the same value. func newOperationKey() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { fail(err.Error()) } b[6] = b[6]&0x0f | 0x40 b[8] = b[8]&0x3f | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) } ``` ## First frame and last frame Put each image in an `image_url` item with `role` set to `first_frame` or `last_frame`. With a single image you may leave out `role` and it becomes the first frame; with several images every one needs a `role`. ```json "content": [ {"type": "text", "text": "A paper boat crossing a moonlit lake."}, {"type": "image_url", "role": "first_frame", "image_url": {"url": "https://example.com/first.png"}}, {"type": "image_url", "role": "last_frame", "image_url": {"url": "https://example.com/last.png"}} ] ``` ## Reference images Set `role` to `reference_image`, with up to 9 images. This example references a registered asset: ```bash IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)} printf '%s\n' "Save IDEMPOTENCY_KEY for retries: ${IDEMPOTENCY_KEY}" >&2 ASSET_ID_PATTERN='^ast-[0-9a-f]{32}$' if [[ ! ${ASSET_ID} =~ ${ASSET_ID_PATTERN} || ${ASSET_ID} == *[!A-Za-z0-9._-]* ]]; then printf '%s\n' 'Set ASSET_ID to the ID from the creation response first.' >&2 exit 1 fi curl --request POST "${CHENVOYA_BASE_URL}/v1/video/generations" \ --header "Authorization: Bearer ${CHENVOYA_API_KEY}" \ --header "Idempotency-Key: ${IDEMPOTENCY_KEY}" \ --header 'Content-Type: application/json' \ --data '{ "model": "dreamina-seedance-2-0-260128", "duration": 5, "ratio": "16:9", "resolution": "720p", "content": [ { "type": "text", "text": "Animate the reviewed reference image." }, { "type": "image_url", "role": "reference_image", "image_url": { "url": "asset://'"${ASSET_ID}"'" } } ] }' ``` ```python import urllib.request import urllib.parse import json import os import sys import re import uuid key = os.environ["CHENVOYA_API_KEY"].strip() if not key or re.search(r"[\x00-\x1f\x7f-\x9f]", key): sys.exit("Set CHENVOYA_API_KEY to a non-empty value without control characters") base = os.environ["CHENVOYA_BASE_URL"].rstrip("/") headers = {"Authorization": "Bearer " + key} idempotency = os.environ.get("IDEMPOTENCY_KEY") or str(uuid.uuid4()) print("Save IDEMPOTENCY_KEY for retries: " + idempotency, file=sys.stderr) headers["Idempotency-Key"] = idempotency path = "/v1/video/generations" body_text = "{\"model\":\"dreamina-seedance-2-0-260128\",\"duration\":5,\"ratio\":\"16:9\",\"resolution\":\"720p\",\"content\":[{\"type\":\"text\",\"text\":\"Animate the reviewed reference image.\"},{\"type\":\"image_url\",\"role\":\"reference_image\",\"image_url\":{\"url\":\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"}}]}" ASSET_ID = os.environ["ASSET_ID"] if not re.fullmatch("^ast-[0-9a-f]{32}$", ASSET_ID): sys.exit("Set ASSET_ID to the ID from the creation response") body_text = body_text.replace("\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"", json.dumps("asset://" + ASSET_ID)) headers["Content-Type"] = "application/json" body = json.dumps(json.loads(body_text)).encode("utf-8") request = urllib.request.Request(base + path, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(request) as response: print(response.read().decode("utf-8")) except urllib.request.HTTPError as error: print(error.read().decode("utf-8")) sys.exit(1) ``` ```javascript // Needs Node.js 20 or later; save it as a .mjs file and run it. const key = process.env.CHENVOYA_API_KEY?.trim(); if (!key || /[\x00-\x1f\x7f-\x9f]/.test(key)) { console.error("Set CHENVOYA_API_KEY to a non-empty value without control characters"); process.exit(1); } const base = process.env.CHENVOYA_BASE_URL; if (!base) throw new Error("Set CHENVOYA_BASE_URL"); const headers = {Authorization: `Bearer ${key}`}; const idempotency = process.env.IDEMPOTENCY_KEY || crypto.randomUUID(); console.error("Save IDEMPOTENCY_KEY for retries: " + idempotency); headers["Idempotency-Key"] = idempotency; const path = "/v1/video/generations"; let bodyText = "{\"model\":\"dreamina-seedance-2-0-260128\",\"duration\":5,\"ratio\":\"16:9\",\"resolution\":\"720p\",\"content\":[{\"type\":\"text\",\"text\":\"Animate the reviewed reference image.\"},{\"type\":\"image_url\",\"role\":\"reference_image\",\"image_url\":{\"url\":\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"}}]}"; const ASSET_ID = process.env.ASSET_ID; if (typeof ASSET_ID !== "string" || (new RegExp("^ast-[0-9a-f]{32}$")).exec(ASSET_ID)?.[0] !== ASSET_ID) throw new Error("Set ASSET_ID to the ID from the creation response"); bodyText = bodyText.replaceAll("\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"", JSON.stringify("asset://" + ASSET_ID)); headers["Content-Type"] = "application/json"; const body = JSON.stringify(JSON.parse(bodyText)); const response = await fetch(base.replace(/\/$/, "") + path, {method: "POST", headers, body: body}); console.log(await response.text()); if (!response.ok) process.exitCode = 1; ``` ```go package main import ( "crypto/rand" "fmt" "io" "net/http" "os" "regexp" "strconv" "strings" ) func main() { key := strings.TrimSpace(os.Getenv("CHENVOYA_API_KEY")) if key == "" || regexp.MustCompile(`[\x00-\x1f\x7f-\x9f]`).MatchString(key) { fail("Set CHENVOYA_API_KEY to a non-empty value without control characters") } base := strings.TrimRight(os.Getenv("CHENVOYA_BASE_URL"), "/") if base == "" { fail("Set CHENVOYA_BASE_URL") } path := "/v1/video/generations" bodyText := "{\"model\":\"dreamina-seedance-2-0-260128\",\"duration\":5,\"ratio\":\"16:9\",\"resolution\":\"720p\",\"content\":[{\"type\":\"text\",\"text\":\"Animate the reviewed reference image.\"},{\"type\":\"image_url\",\"role\":\"reference_image\",\"image_url\":{\"url\":\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"}}]}" assetID := os.Getenv("ASSET_ID") if !regexp.MustCompile(`\A(?:ast-[0-9a-f]{32})\z`).MatchString(assetID) { fail("Set ASSET_ID to the ID from the creation response") } bodyText = strings.ReplaceAll(bodyText, "\"asset://ast-bca04b1132034b9b42cb51fe29e9a0a7\"", strconv.Quote("asset://"+assetID)) request, err := http.NewRequest("POST", base+path, strings.NewReader(bodyText)) if err != nil { fail(err.Error()) } request.Header.Set("Authorization", "Bearer "+key) request.Header.Set("Content-Type", "application/json") idempotency := os.Getenv("IDEMPOTENCY_KEY") if idempotency == "" { idempotency = newOperationKey() } fmt.Fprintln(os.Stderr, "Save IDEMPOTENCY_KEY for retries: "+idempotency) request.Header.Set("Idempotency-Key", idempotency) response, err := http.DefaultClient.Do(request) if err != nil { fail(err.Error()) } text, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { fail(err.Error()) } fmt.Println(string(text)) if response.StatusCode < 200 || response.StatusCode > 299 { os.Exit(1) } } func fail(message string) { fmt.Fprintln(os.Stderr, message) os.Exit(1) } // newOperationKey returns a random UUID; save it to retry with the same value. func newOperationKey() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { fail(err.Error()) } b[6] = b[6]&0x0f | 0x40 b[8] = b[8]&0x3f | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) } ``` ## Reference video and audio - Reference videos use `video_url` items with `role` `reference_video`; reference audio uses `audio_url` items with `role` `reference_audio`. - Each clip lasts 2–15 seconds; the videos together and the audio clips together each stay within 15 seconds, with at most 3 videos and 3 audio clips. - Reference audio needs a reference image or video. - A reference video given as an HTTPS link reserves funds for 15 input seconds. Preparation checks the actual length: a task whose total is too long fails with `input_duration_exceeded`, the whole reservation is released and nothing is charged. ## Media sources Each image, video or audio item takes one of three sources: | Source | Form | Notes | | --- | --- | --- | | HTTPS link | `https://…` | The platform downloads, checks and stores the file before generation; meanwhile the task is in `preparing_assets`. If preparation fails, the whole reservation is released and nothing is charged. | | Registered asset | `asset://` and the asset ID | [Register the asset](./api-docs-en-assets.md) first and reference it once it is `active`. | | base64 data URI | `data:image/png;base64,…` | Images in JPEG, PNG or WebP, smaller than 30 MB once decoded; audio in WAV or MP3, up to 15 MB and 2–15 seconds; not for video. | HTTPS links use port 443 and carry no user information, IP address or local host name, at most 2048 bytes. One request may combine HTTPS links and base64 data, but neither can be mixed with `asset://` references. ## Sound, aspect ratio and duration - `generate_audio`: whether to generate sound with the video; default `false`. - `ratio`: aspect ratio; default `adaptive`, chosen by the model from the input. - `duration`: 4–15 seconds; default 5. - `resolution`: default `720p`; Seedance 2.0 Fast and Mini go up to `720p`. - `watermark`, `seed`, `return_last_frame`: see [Video models](./api-docs-en-video-models.md#outputs). ## Large requests A body can be up to 64 MiB. A body over 2 MiB must arrive within 120 seconds, and each team can send only a few large requests at the same time; when they are all in use the answer is `429 rate_limited`: retry later with the same `Idempotency-Key`. ## Next steps - [Task status and results](./api-docs-en-video-tasks.md): What the task states mean, polling or callbacks, downloading results and cancelling tasks. - [Upload assets](./api-docs-en-assets.md): Register an asset, wait until it is active, then reference it with asset:// in generation requests. - [Create a video task](./api-docs-en-api-video-tasks-create.md): Submit a prompt with optional images, videos or audio to create an asynchronous generation task; the task ID and the reserved amount come back at once.