# Register an asset `POST /v1/assets` Requires service.use. Register an image, audio clip or video at a public HTTPS address. After acceptance, the file is fetched and checked asynchronously. Idempotency belongs to your team and this operation: the same request returns the saved response; a different request with the same value is rejected. Models without asset input support return invalid_request without creating an asset, intake job or reservation. - Put your API key after `Authorization: Bearer`; the key needs `service.use` permission. - Send an `Idempotency-Key`; the same request replays the original response and a different one is refused. - After acceptance the file is fetched and checked in the background; read its status at `Location`. - Only `active` assets can be referenced with `asset://` in generation requests. ## Headers | Field | Type | Requirement | Description | | --- | --- | --- | --- | | `Authorization` | string | Required | Bearer, a space and your API key. A missing or invalid key returns 401 authentication_failed. | | `Content-Type` | string | Required | The body is JSON: application/json. | | `X-Request-ID` | string | Optional | Your own request identifier. An invalid or repeated value is replaced; the value used comes back in the X-Request-ID response header. | | `Idempotency-Key` | string | Required | Generate a new value for every new request and save it; it starts with a letter or digit. When the outcome is unknown, retry with the same key and the original body to get the original response; the same key with a different body returns 409 idempotency_conflict. Keys belong to your team, not to one API key. Length: 8–128. Characters: A–Z a–z 0–9 . _ : -. | ## Request body · application/json · Required The body accepts only the fields below; any other field is rejected. | Field | Type | Requirement | Description | | --- | --- | --- | --- | | `url` | string | Required | Public HTTPS address of the source file, up to 2048 UTF-8 bytes. User information in the URL is rejected, as are destinations outside the fetch policy. Length: 9–2048. Format: https://. | | `media_type` | string | Required | The kind of media in this file. `image`: Image media; `audio`: Audio media; `video`: Video media; | | `model` | string | Required | ID of the model used for this request. `dreamina-seedance-2-0-260128`: Seedance 2.0; `dreamina-seedance-2-0-fast-260128`: Seedance 2.0 Fast; `dreamina-seedance-2-0-mini-260615`: Seedance 2.0 Mini; | | `name` | string | Optional | Optional display name. Control characters, line or paragraph separators and bidirectional text controls are not accepted. Length: 1–64. | ## Returns · HTTP 202 Asset intake was accepted. Follow Location to check its progress. | Field | Type | Requirement | Description | | --- | --- | --- | --- | | `asset_id` | string | Required | ID assigned to the asset by the platform. Length: 36–36. Format: ast- and 32 hex digits. | | `status` | string | Required | Current state; see the value notes for its meaning. Always: processing. | ### Response headers | Field | Type | Requirement | Description | | --- | --- | --- | --- | | `X-Request-ID` | string | Required | The identifier of this request: the valid value you sent, or its replacement. | | `Cache-Control` | string | Required | Instructs clients not to cache the response. | | `Location` | string | Required | Canonical relative lookup path for the accepted object. | ## Status codes Each `error.code` and what to do about it is on [Error codes](./api-docs-en-api-errors.md). | Status | Meaning | error.code | | --- | --- | --- | | 202 | Asset intake was accepted. Response headers: `Cache-Control`, `Location` | | | 400 | The request is invalid or the source address fails the HTTPS fetch policy. | invalid_request, source_url_not_allowed | | 401 | The API key is missing or invalid. Response headers: `WWW-Authenticate` | authentication_failed | | 403 | This API key does not have service.use permission. | permission_denied | | 409 | Your team already used this idempotency value for a different canonical asset registration request. | idempotency_conflict | | 410 | The saved acceptance response is too old to replay exactly. | idempotency_replay_expired | | 429 | The asset cannot be accepted within your team's stored-byte allowance or intake capacity. | asset_intake_rate_limit_exceeded, asset_storage_quota_exceeded | | 503 | The service is temporarily unavailable. | dependency_unavailable | ## Related guides - [Upload assets](./api-docs-en-assets.md) - [Idempotency and retries](./api-docs-en-idempotency.md) ## Request examples ```bash 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" }' ``` ```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("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) ``` ```javascript // 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; ``` ```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/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]) } ``` ## Response examples 202 ```json { "asset_id": "ast-11111111111111111111111111111111", "status": "processing" } ``` 400 ```json { "error": { "code": "invalid_request", "message": "invalid asset request" }, "trace_id": "trace_asset_invalid" } ``` 401 ```json { "error": { "code": "authentication_failed", "message": "authentication failed" }, "trace_id": "trace_error_example" } ``` 403 ```json { "error": { "code": "permission_denied", "message": "permission denied" }, "trace_id": "trace_asset_permission" } ``` 409 ```json { "error": { "code": "idempotency_conflict", "message": "idempotency key conflicts with the original request" }, "trace_id": "trace_asset_conflict" } ``` 410 ```json { "error": { "code": "idempotency_replay_expired", "message": "asset create replay expired" }, "trace_id": "trace_asset_expired" } ``` 429 ```json { "error": { "code": "asset_storage_quota_exceeded", "message": "asset storage quota exceeded" }, "trace_id": "trace_asset_quota" } ``` 503 ```json { "error": { "code": "dependency_unavailable", "message": "service is not ready" }, "trace_id": "trace_error_example" } ```