任务状态与结果
任务状态的含义、轮询和回调两种取结果方式、下载结果和取消任务。
本页内容
创建任务后,用任务 ID 查询进度和结果,或者等回调通知。
任务状态
queuedprocessingsucceeded另外两个终态已失败failed已取消cancelled都不收费。
任务处于 processing 时,phase 说明当前阶段:准备素材、等待处理、准备生成、生成中、确认结果中、整理结果、等待结算。全部取值见「状态与原因」。
三个状态字段
| 字段 | 说明 |
|---|---|
status | 任务执行状态,终态是 succeeded、failed 和 cancelled。 |
delivery_status | 结果文件的交付进度,available 时可以下载。 |
settlement_status | 费用结算进度,settled 后费用确定。 |
轮询
每隔几秒查询一次,直到 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)
}回调
在控制台登记并验证回调地址后,创建任务时传 callback_url,任务结束会收到 task.completed。通知不含下载地址,收到后再查询一次任务。详见「接收回调」。
下载结果
成功的任务在 output.assets 里列出结果文件,role 为 result。url 是 HTTPS 下载地址,每次查询任务都会重新签发,最长 24 小时有效,且不晚于 expires_at(默认结果准备好后 7 天);过期或暂不可用时 url 为空,download_status 说明原因。请在有效期内下载到自己的存储。
curl --output video.mp4 "$VIDEO_URL"列出任务
GET /v1/video/tasks 列出团队的全部任务,不限于创建任务的那一把 API 密钥,按创建时间从新到旧排列。列表只含标识、模型、状态和时间;用量、费用、结果和错误请按任务 ID 查询。
curl --request GET "${CHENVOYA_BASE_URL}/v1/video/tasks" \
--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}
path = "/v1/video/tasks"
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 path = "/v1/video/tasks";
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"
"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/tasks"
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)
}取消任务
POST /v1/video/tasks/{task_id}/cancel 需要 Idempotency-Key,请求体为空。
- 生成开始前取消立即生效:返回
effective,任务变为cancelled,预留全额释放。 - 生成开始后返回
not_effective,原因码generation_already_started,任务继续生成并照常结算。 - 已经结束、又没有取消操作的任务返回
409 task_not_cancellable。
IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)}
printf '%s\n' "保存 IDEMPOTENCY_KEY,重试时原样复用:${IDEMPOTENCY_KEY}" >&2
curl --request POST "${CHENVOYA_BASE_URL}/v1/video/tasks/${TASK_ID}/cancel" \
--header "Authorization: Bearer ${CHENVOYA_API_KEY}" \
--header "Idempotency-Key: ${IDEMPOTENCY_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}
idempotency = os.environ.get("IDEMPOTENCY_KEY") or str(uuid.uuid4())
print("保存 IDEMPOTENCY_KEY,重试时原样复用:" + idempotency, file=sys.stderr)
headers["Idempotency-Key"] = idempotency
TASK_ID = os.environ["TASK_ID"]
path = "/v1/video/tasks/{task_id}/cancel".replace("{task_id}", urllib.parse.quote(TASK_ID, safe=""))
request = urllib.request.Request(base + path, data=None, 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 TASK_ID = process.env.TASK_ID;
if (!TASK_ID) throw new Error("Set TASK_ID");
const path = "/v1/video/tasks/{task_id}/cancel".replace("{task_id}", encodeURIComponent(TASK_ID));
const response = await fetch(base.replace(/\/$/, "") + path, {method: "POST", headers, body: undefined});
console.log(await response.text());
if (!response.ok) process.exitCode = 1;package main
import (
"crypto/rand"
"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}/cancel", "{task_id}", url.PathEscape(taskID), 1)
request, err := http.NewRequest("POST", base+path, nil)
if err != nil {
fail(err.Error())
}
request.Header.Set("Authorization", "Bearer "+key)
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])
}失败原因
失败的任务在 error.code 里给出原因,例如素材下载失败的 input_fetch_failed、生成失败的 generation_failed。全部取值见「状态与原因」。失败或取消的任务结算后费用为零。