Upload assets
Register an asset, wait until it is active, then reference it with asset:// in generation requests.
On this page
An asset is an image, video or audio file registered with the platform. Register it once and reference it in as many generation requests as you like.
When to use assets
An image, video or audio input of a generation request comes from one of three sources: an HTTPS link, a base64 data URI or a registered asset. Registering pays off when you use the same file again and again; for a single use, put the link in the request. The rules of each source are on Generate a video.
Register an asset
Send the public HTTPS address, the media type and the model to POST /v1/assets. Registration needs an Idempotency-Key, and the API key needs service.use permission.
IDEMPOTENCY_KEY=${IDEMPOTENCY_KEY:-$(uuidgen)}
printf '%s\n' "Save IDEMPOTENCY_KEY for retries: ${IDEMPOTENCY_KEY}" >&2
curl --request POST "${CHENVOYA_BASE_URL}/v1/assets" \
--header "Authorization: Bearer ${CHENVOYA_API_KEY}" \
--header "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
--header 'Content-Type: application/json' \
--data '{
"url": "https://images.example.test/reference.png",
"media_type": "image",
"model": "dreamina-seedance-2-0-260128",
"name": "reference"
}'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
path = "/v1/assets"
body_text = "{\"url\":\"https://images.example.test/reference.png\",\"media_type\":\"image\",\"model\":\"dreamina-seedance-2-0-260128\",\"name\":\"reference\"}"
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)// 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 path = "/v1/assets";
let bodyText = "{\"url\":\"https://images.example.test/reference.png\",\"media_type\":\"image\",\"model\":\"dreamina-seedance-2-0-260128\",\"name\":\"reference\"}";
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/assets"
bodyText := "{\"url\":\"https://images.example.test/reference.png\",\"media_type\":\"image\",\"model\":\"dreamina-seedance-2-0-260128\",\"name\":\"reference\"}"
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, "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])
}After acceptance the platform downloads and checks the file in the background; the Location response header is the path to read.
{
"asset_id": "ast-11111111111111111111111111111111",
"status": "processing"
}Read the status
Read GET /v1/assets/{asset_id} until the status is no longer processing:
| Status | Meaning |
|---|---|
processing | Being downloaded and checked; it cannot be referenced yet. |
active | Ready to reference; size and digest are returned, with the dimensions of images and videos and the duration of audio and video. |
unavailable | Cannot be used; failure_reason says why. |
curl --request GET "${CHENVOYA_BASE_URL}/v1/assets/${ASSET_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}
ASSET_ID = os.environ["ASSET_ID"]
if not re.fullmatch("^ast-[0-9a-f]{32}$", ASSET_ID):
sys.exit("Set ASSET_ID to the ID from the creation response")
path = "/v1/assets/{asset_id}".replace("{asset_id}", urllib.parse.quote(ASSET_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 ASSET_ID = process.env.ASSET_ID;
if (!ASSET_ID) throw new Error("Set ASSET_ID");
if (typeof ASSET_ID !== "string" || (new RegExp("^ast-[0-9a-f]{32}$")).exec(ASSET_ID)?.[0] !== ASSET_ID) throw new Error("Set ASSET_ID to the ID from the creation response");
const path = "/v1/assets/{asset_id}".replace("{asset_id}", encodeURIComponent(ASSET_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")
}
assetID := os.Getenv("ASSET_ID")
if assetID == "" {
fail("Set ASSET_ID")
}
if !regexp.MustCompile(`\A(?:ast-[0-9a-f]{32})\z`).MatchString(assetID) {
fail("Set ASSET_ID to the ID from the creation response")
}
path := strings.Replace("/v1/assets/{asset_id}", "{asset_id}", url.PathEscape(assetID), 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)
}Reference an asset
Write asset:// and the asset ID in the url of an image_url, video_url or audio_url item. An asset registered for one model can also be used by requests for other compatible models.
- Referencing an asset that is still processing returns
409 asset_not_ready; once it isactive, retry with the sameIdempotency-Key. - An asset of another team, an unknown one and a deleted one all return
asset_not_found. - One request cannot mix
asset://references with HTTPS links or base64 data.
Addresses and allowances
- The source must be a public HTTPS address of at most 2048 bytes without user information; an unsafe destination returns
400 source_url_not_allowed. - When your team's storage allowance is used up the answer is
429 asset_storage_quota_exceeded; when intake capacity is limited it is429 asset_intake_rate_limit_exceeded: retry later.