# 快速开始 三步生成第一个视频:创建 API 密钥,提交任务,取回结果。 你需要一个已开通的账户,余额够预留一次生成。下面以 Seedance 2.0 的文生视频为例,时长、分辨率和画幅都用默认值(5 秒、720p、自适应)。 **接入地址**:向开户人员索取接入地址。 ## 1. 创建 API 密钥 在控制台的「[API 密钥](./integration.html)」页点「创建 API 密钥」,把它和上面的接入地址存进服务器的环境变量。同一个 API 密钥可以调用全部模型。 ```bash export CHENVOYA_BASE_URL="接入地址" export CHENVOYA_API_KEY="你的 API 密钥" ``` > **注意**:API 密钥只在服务器端使用,不要写进网页、App 或代码仓库,也不要贴进文档或日志。怀疑泄露时,在控制台停用它并新建一个。 ## 2. 提交生成任务 向 `POST /v1/video/generations` 发送模型和提示词。每次创建都带一个新的 `Idempotency-Key`:请求超时或断线时,用同一个键原样重试,不会重复创建任务,也不会重复扣费。 ```bash 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." } ] }' ``` ```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("保存 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) ``` ```javascript // 需要 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; ``` ```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, "保存 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,生成在后台进行。 响应 202(节选): ```json { "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`),完成后按实际用量结算,多出的部分释放;失败或取消的任务不收费。 ## 3. 取回结果 任务按下面的顺序推进,已成功、已失败和已取消都是终态。 `queued` (排队中) → `processing` (处理中) → `succeeded` (已成功);另外两个终态 `failed` (已失败) 和 `cancelled` (已取消) 都不收费。 ### 轮询任务 每隔几秒查询一次 `GET /v1/video/tasks/{task_id}`,直到 `status` 变成终态。 ```bash curl --request GET "${CHENVOYA_BASE_URL}/v1/video/tasks/${TASK_ID}" \ --header "Authorization: Bearer ${CHENVOYA_API_KEY}" ``` ```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} 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) ``` ```javascript // 需要 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; ``` ```go 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) } ``` 响应 200(节选): ```json { "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 天);再次查询任务可拿到新地址,过期后为空。把视频下载到自己的存储: ```bash curl --output video.mp4 "$VIDEO_URL" ``` ### 接收回调 不想轮询,可以让我们通知你:在控制台的「[回调](./callbacks.html)」页添加并验证一个 HTTPS 地址,创建任务时放进请求体的 `callback_url`。任务结束后你会收到 `task.completed`,费用结算后再收到 `task.billing_settled`。处理前先[验证签名](./api-docs-zh-callbacks-verify.md)。 ```json "callback_url": "https://example.com/webhooks/chenvoya" ``` ## 下一步 - [生成视频](./api-docs-zh-video-generate.md):文生视频、首帧、首尾帧、参考图和参考音视频的请求写法。 - [计费方式](./api-docs-zh-billing.md):创建时按上限预留,完成后按实际用量结算,失败和取消的任务不收费。 - [创建视频任务](./api-docs-zh-api-video-tasks-create.md):提交提示词和可选的图片、视频或音频,创建一个异步生成任务,立即返回任务 ID 和预留金额。