快速开始
三步生成第一个视频:创建 API 密钥,提交任务,取回结果。
本页内容
你需要一个已开通的账户,余额够预留一次生成。下面以 Seedance 2.0 的文生视频为例,时长、分辨率和画幅都用默认值(5 秒、720p、自适应)。
接入地址
正在读取接入地址…所有请求路径都接在这个地址后面,例如 POST /v1/video/generations。示例把它放在 CHENVOYA_BASE_URL 环境变量里,末尾不带斜杠。
创建 API 密钥
在控制台的「API 密钥」页点「创建 API 密钥」,把它和上面的接入地址存进服务器的环境变量。同一个 API 密钥可以调用全部模型。
export CHENVOYA_BASE_URL="接入地址"
export CHENVOYA_API_KEY="你的 API 密钥"注意:API 密钥只在服务器端使用,不要写进网页、App 或代码仓库,也不要贴进文档或日志。怀疑泄露时,在控制台停用它并新建一个。
提交生成任务
向 POST /v1/video/generations 发送模型和提示词。每次创建都带一个新的 Idempotency-Key:请求超时或断线时,用同一个键原样重试,不会重复创建任务,也不会重复扣费。
IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)}
printf '%s\n' "保存 IDEMPOTENCY_KEY,重试时原样复用:${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("保存 IDEMPOTENCY_KEY,重试时原样复用:" + 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)// 需要 Node.js 20 及以上版本,保存为 .mjs 文件后运行。
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("保存 IDEMPOTENCY_KEY,重试时原样复用:" + 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, "保存 IDEMPOTENCY_KEY,重试时原样复用:"+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])
}接口立即返回 202 和任务 ID,生成在后台进行。
{
"id": "task_32ae2a5177b310d3bc1ec83a98f63889",
"object": "video.task",
"model": "dreamina-seedance-2-0-260128",
"status": "queued",
"settlement_status": "reserved",
"quote": {
"currency": "USD",
"reserved_amount": "0.782453",
"estimated_usage_upper_bound": {
"video_tokens": 111779
}
},
"created_at": 1793167380
}提示:金额先按请求的上限预留(quote.reserved_amount),完成后按实际用量结算,多出的部分释放;失败或取消的任务不收费。
取回结果
任务按下面的顺序推进,已成功、已失败和已取消都是终态。
queuedprocessingsucceeded另外两个终态已失败failed已取消cancelled都不收费。
轮询任务
每隔几秒查询一次 GET /v1/video/tasks/{task_id},直到 status 变成终态。
curl --request GET "${CHENVOYA_BASE_URL}/v1/video/tasks/${TASK_ID}" \
--header "Authorization: Bearer ${CHENVOYA_API_KEY}"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}
TASK_ID = os.environ["TASK_ID"]
path = "/v1/video/tasks/{task_id}".replace("{task_id}", urllib.parse.quote(TASK_ID, safe=""))
request = urllib.request.Request(base + path, data=None, headers=headers, method="GET")
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)// 需要 Node.js 20 及以上版本,保存为 .mjs 文件后运行。
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 TASK_ID = process.env.TASK_ID;
if (!TASK_ID) throw new Error("Set TASK_ID");
const path = "/v1/video/tasks/{task_id}".replace("{task_id}", encodeURIComponent(TASK_ID));
const response = await fetch(base.replace(/\/$/, "") + path, {method: "GET", headers, body: undefined});
console.log(await response.text());
if (!response.ok) process.exitCode = 1;package main
import (
"fmt"
"io"
"net/http"
"net/url"
"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")
}
taskID := os.Getenv("TASK_ID")
if taskID == "" {
fail("Set TASK_ID")
}
path := strings.Replace("/v1/video/tasks/{task_id}", "{task_id}", url.PathEscape(taskID), 1)
request, err := http.NewRequest("GET", base+path, nil)
if err != nil {
fail(err.Error())
}
request.Header.Set("Authorization", "Bearer "+key)
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)
}{
"id": "task_32ae2a5177b310d3bc1ec83a98f63889",
"object": "video.task",
"status": "succeeded",
"output": {
"assets": [
{
"media_type": "video",
"role": "result",
"url": "https://media.example.invalid/download/result-0001?signature=example",
"expires_at": 1793253785
}
]
}
}成功后,output.assets[0].url 就是视频的下载地址,最长 24 小时有效,且不晚于 expires_at(默认结果准备好后 7 天);再次查询任务可拿到新地址,过期后为空。把视频下载到自己的存储:
curl --output video.mp4 "$VIDEO_URL"接收回调
不想轮询,可以让我们通知你:在控制台的「回调」页添加并验证一个 HTTPS 地址,创建任务时放进请求体的 callback_url。任务结束后你会收到 task.completed,费用结算后再收到 task.billing_settled。处理前先验证签名。
"callback_url": "https://example.com/webhooks/chenvoya"