Task status and results
What the task states mean, polling or callbacks, downloading results and cancelling tasks.
On this page
After you create a task, read its progress and result by task ID, or wait for a callback.
Task status
queuedprocessingsucceededThe other final statesFailedfailedCancelledcancelledare free.
While a task is processing, phase names the current stage: preparing inputs, waiting, starting, generating, confirming the result, finishing and awaiting settlement. Every value is on Statuses and reasons.
Three status fields
| Field | Meaning |
|---|---|
status | Task execution; the final states are succeeded, failed and cancelled. |
delivery_status | Delivery of the result files; download them once it is available. |
settlement_status | Billing settlement; the charge is final once it is settled. |
Poll
Read the task every few seconds until status is final:
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)// 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 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)
}Callbacks
After you register and verify a callback address in the console, pass callback_url when you create a task and you receive task.completed when it finishes. The event has no download link: read the task once more. See Receive callbacks.
Download the result
A succeeded task lists its result files in output.assets with role result. url is an HTTPS download link; each task read signs a fresh one that works for up to 24 hours and never after expires_at (7 days after the result is ready by default); when it has expired or is not available, url is null and download_status says why. Save the file to your own storage in time.
curl --output video.mp4 "$VIDEO_URL"List tasks
GET /v1/video/tasks lists all of your team's tasks, whichever API key created them, newest first. A list entry holds the IDs, model, statuses and times; read usage, charges, results and errors from the task by its 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)// 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 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)
}Cancel a task
POST /v1/video/tasks/{task_id}/cancel needs an Idempotency-Key and no body.
- Before generation starts, cancellation takes effect at once: the answer is
effective, the task becomescancelledand the whole reservation is released. - After generation starts, the answer is
not_effectivewith reasongeneration_already_started; the task continues and is settled as usual. - A finished task without a cancellation returns
409 task_not_cancellable.
IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)}
printf '%s\n' "Save IDEMPOTENCY_KEY for retries: ${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("Save IDEMPOTENCY_KEY for retries: " + 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)// 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 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, "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])
}Failure reasons
A failed task gives its reason in error.code, such as input_fetch_failed when an input could not be downloaded or generation_failed. Every value is on Statuses and reasons. Failed and cancelled tasks are settled at no charge.