Generate a video
Requests for text to video, first frames, first and last frames, reference images and reference media.
On this page
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.
Text to video
Put a single text item in content that describes the scene, the action and the camera.
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."
}
]
}'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)// 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;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.
"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:
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}"'"
}
}
]
}'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)// 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;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_urlitems withrolereference_video; reference audio usesaudio_urlitems withrolereference_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 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; defaultfalse.ratio: aspect ratio; defaultadaptive, chosen by the model from the input.duration: 4–15 seconds; default 5.resolution: default720p; Seedance 2.0 Fast and Mini go up to720p.watermark,seed,return_last_frame: see Video models.
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.