# 取消视频任务 `POST /v1/video/tasks/{task_id}/cancel` 需要 service.use 权限,必须提供 Idempotency-Key,请求体必须为空。生成开始前取消立即生效,任务变为 cancelled,全额释放预留;资金处理挂起时仍返回 effective,随后完成释放。生成开始后返回 not_effective,原任务继续生成并照常结算。相同操作键重放原响应;同键换任务返回 idempotency_conflict。新键引用同一取消操作并冻结当时任务状态;没有取消操作的终态任务返回 task_not_cancellable。未知或其他团队任务与查询接口一样返回 task_not_found,拒绝请求不保留操作键。 - 在请求头 `Authorization: Bearer` 后面放 API 密钥;请求体为空。 - 必须带 `Idempotency-Key`;同一个键重放原响应。 - 生成开始前取消立即生效,任务变为 `cancelled`,预留全额释放。 - 生成开始后返回 `not_effective`,任务继续生成并照常结算。 ## 请求头 | 字段 | 类型 | 要求 | 说明 | | --- | --- | --- | --- | | `Authorization` | string | 必填 | Bearer 加一个空格和 API 密钥。缺失或无效时返回 401 authentication_failed。 | | `X-Request-ID` | string | 可选 | 你自己的请求标识。无效或重复的值会被替换,最终值在响应头 X-Request-ID 返回。 | | `Idempotency-Key` | string | 必填 | 每个新请求生成一个新值并保存,以字母或数字开头。结果不明时用同一个键和原请求体重试,会返回原响应;同一个键配不同的请求体返回 409 idempotency_conflict。键按团队区分,不按 API 密钥区分。 长度:8–128。 字符:A–Z a–z 0–9 . _ : -。 | ## 路径参数 | 字段 | 类型 | 要求 | 说明 | | --- | --- | --- | --- | | `task_id` | string | 必填 | 平台视频任务标识 | ## 返回 · HTTP 202 不可变的取消操作快照;effective 表示已取消,not_effective 表示本次取消未生效,须继续查询任务与账单。 | 字段 | 类型 | 要求 | 说明 | | --- | --- | --- | --- | | `task_id` | string | 必填 | 本团队的视频任务标识。 长度:4–128。 字符:A–Z a–z 0–9 _ -。 | | `cancel_operation_id` | string | 必填 | 取消操作的稳定标识,同一任务的新操作键引用同一个标识。 长度:4–128。 字符:A–Z a–z 0–9 _ -。 | | `cancel_status` | string | 必填 | 取消操作状态;首发直接回答已生效或未生效。 `requested`:已请求取消; `effective`:取消已生效; `not_effective`:取消未生效; `failed`:失败; | | `task_status_at_response` | string | 必填 | 该操作键首次应答时冻结的任务状态,重放时不会更新。 `queued`:排队中; `processing`:处理中; `succeeded`:已成功; `failed`:已失败; `cancelled`:已取消; | | `reason_code` | string | 必填,可能为空 | 已生效时为空;生成已开始而未生效时为 generation_already_started。 长度:1–96。 字符:a–z 0–9 _。 | ### 响应头 | 字段 | 类型 | 要求 | 说明 | | --- | --- | --- | --- | | `X-Request-ID` | string | 必填 | 本次请求的标识:你提供的有效值,或替换后的值。 | | `Location` | string | 必填 | 已接受对象的规范查询路径。 | ## 状态码 每个 `error.code` 的处理办法见「[错误码](./api-docs-zh-api-errors.md)」。 | 状态码 | 说明 | error.code | | --- | --- | --- | | 202 | 不可变的取消操作快照;effective 表示已取消,not_effective 表示本次取消未生效,须继续查询任务与账单。 响应头:`Location` | | | 400 | 请求无效。 | invalid_request | | 401 | API 密钥缺失或无效。 响应头:`WWW-Authenticate` | authentication_failed | | 404 | 任务不存在或对本团队不可见。 | task_not_found | | 409 | 已有操作键对应不同请求,或终态任务没有可引用的取消操作。 | idempotency_conflict, idempotency_replay_expired, task_not_cancellable | | 429 | 请求频率限制拒绝此非创建操作。 响应头:`Retry-After` | rate_limited | | 500 | 服务内部错误。 | internal_error | | 503 | 服务暂时不可用。 | dependency_unavailable | ## 相关指南 - [任务状态与结果](./api-docs-zh-video-tasks.md) - [幂等与重试](./api-docs-zh-idempotency.md) - [计费方式](./api-docs-zh-billing.md) ## 请求示例 ```bash 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}" ``` ```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 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) ``` ```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 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; ``` ```go 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]) } ``` ## 响应示例 202 ```json { "task_id": "task_32ae2a5177b310d3bc1ec83a98f63889", "cancel_operation_id": "cancelop_ab99971fbec36056096527b7494b2059", "cancel_status": "requested", "task_status_at_response": "processing", "reason_code": null } ``` 400 ```json { "error": { "code": "invalid_request", "message": "request parameters are invalid" }, "trace_id": "trace_error_example" } ``` 401 ```json { "error": { "code": "authentication_failed", "message": "authentication failed" }, "trace_id": "trace_error_example" } ``` 404 ```json { "error": { "code": "task_not_found", "message": "task was not found" }, "trace_id": "trace_error_example" } ``` 409 ```json { "error": { "code": "task_not_cancellable", "message": "the terminal task has no cancellation operation" }, "trace_id": "trace_error_example" } ``` 429 ```json { "error": { "code": "rate_limited", "message": "request rate limit exceeded" }, "trace_id": "trace_error_example" } ``` 500 ```json { "error": { "code": "internal_error", "message": "internal server error" }, "trace_id": "trace_error_example" } ``` 503 ```json { "error": { "code": "dependency_unavailable", "message": "service is not ready" }, "trace_id": "trace_error_example" } ```