Skip to content

Official asset management ​

Use this page to read official asset IDs, obtain download URLs or manage official assets. The workflow is: create an asset group and its official group, upload an asset, prepare it for the target model, then read official details. See Query assets for ordinary asset lists and preparation status.

seedance-2.0-sz, seedance-2.0-sz-fast, seedance-2.0-sz-mini and seedance-2.5-sz support official Volcengine IDs and HTTPS media download URLs for prepared assets registered to your account. MoonApiX asset_id, asset_ref and group_id remain the primary identifiers for asset management and generation.

OperationMethod and pathWhere to pass model
Prepare or resume an official assetPOST /v1/assets/{asset_id}/upstreamJSON body
List registered official assetsGET /v1/assets/upstreamQuery parameter
Read official IDs and the current download URLGET /v1/assets/{asset_id}/upstreamQuery parameter
Rename an official assetPATCH /v1/assets/{asset_id}/upstreamJSON body
Delete an official assetDELETE /v1/assets/{asset_id}/upstreamQuery parameter
Resolve an official ID to your MoonApiX IDPOST /v1/assets/resolveJSON body

Complete workflow ​

The following Bash example requires curl, jq, a local reference.png and a MOONAPIX_API_KEY environment variable. It saves receipts in a restricted directory because media URLs may contain short-lived signatures. Do not write receipts or full URLs to shared logs. If a create or upload outcome is unknown, query the MoonApiX ID already obtained before repeating it.

bash
set -euo pipefail
umask 077
: "${MOONAPIX_API_KEY:?Set MOONAPIX_API_KEY in your environment}"
BASE=https://moonapix.com
MODEL=seedance-2.0-sz
RECEIPTS=$(mktemp -d)
MODEL_BODY=$(jq -nc --arg model "$MODEL" '{model:$model}')

# 1. Create a MoonApiX group, then its official group.
curl --silent --show-error --fail-with-body "$BASE/v1/asset-groups" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"project-a","description":"Reference materials","domain":"media"}' \
  -o "$RECEIPTS/group.json"
GROUP_ID=$(jq -er '.group_id' "$RECEIPTS/group.json")
curl --silent --show-error --fail-with-body "$BASE/v1/asset-groups/$GROUP_ID/upstream" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" \
  -H "Content-Type: application/json" -d "$MODEL_BODY" \
  -o "$RECEIPTS/official-group.json"

# 2. Create a V2 asset in this group and request a managed copy.
curl --silent --show-error --fail-with-body "$BASE/v1/assets" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" \
  -H "Asset-Contract: v2" \
  -F "file=@./reference.png" -F "type=image" \
  -F "group_id=$GROUP_ID" -F 'storage={"enabled":true}' \
  -o "$RECEIPTS/upload.json"
ASSET_ID=$(jq -er '.asset_id' "$RECEIPTS/upload.json")

# 3. Prepare this same asset; HTTP 202 means it is still processing.
curl --silent --show-error --fail-with-body "$BASE/v1/assets/$ASSET_ID/prepare" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" \
  -H "Content-Type: application/json" -d "$MODEL_BODY" \
  -o "$RECEIPTS/prepare.json"
for ATTEMPT in $(seq 1 24); do
  if jq -e '.preparation.generation_ready == true' "$RECEIPTS/prepare.json" >/dev/null; then
    break
  fi
  sleep 10
  curl --silent --show-error --fail-with-body "$BASE/v1/assets/$ASSET_ID/prepare" \
    -H "Authorization: Bearer $MOONAPIX_API_KEY" \
    -H "Content-Type: application/json" -d "$MODEL_BODY" \
    -o "$RECEIPTS/prepare.json"
done
jq -e '.preparation.generation_ready == true' "$RECEIPTS/prepare.json" >/dev/null

# 4. Read current official IDs and the download URL.
curl --silent --show-error --fail-with-body \
  "$BASE/v1/assets/$ASSET_ID/upstream?model=$MODEL" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -o "$RECEIPTS/detail.json"
OFFICIAL_ASSET_ID=$(jq -er '.upstream.asset_id' "$RECEIPTS/detail.json")
CALL_GROUP_ID=$(jq -er '.upstream.group_id' "$RECEIPTS/detail.json")
OFFICIAL_OBSERVED_GROUP_ID=$(jq -er \
  '.upstream.observed_group_id // .upstream.group_id' "$RECEIPTS/detail.json")

# 5. Resolve known official IDs to this account's MoonApiX IDs.
jq -n --arg model "$MODEL" --arg aid "$OFFICIAL_ASSET_ID" --arg gid "$CALL_GROUP_ID" \
  '{model:$model,upstream_asset_id:$aid,upstream_group_id:$gid}' \
  > "$RECEIPTS/resolve-request.json"
curl --silent --show-error --fail-with-body "$BASE/v1/assets/resolve" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -H "Content-Type: application/json" \
  --data-binary "@$RECEIPTS/resolve-request.json" -o "$RECEIPTS/resolved.json"
jq -e --arg aid "$ASSET_ID" --arg gid "$GROUP_ID" \
  '.asset_id == $aid and .group_id == $gid' "$RECEIPTS/resolved.json" >/dev/null

When Prepare returns HTTP 202, continue Prepare for the same asset. Check preparation.generation_ready even after HTTP 200. The example stops after approximately four minutes; stopping does not delete the official asset. Keep the receipts and query or continue Prepare later, rather than uploading again. POST to the asset's /upstream subresource can also start or resume preparation and include upstream once IDs are available. Always read detail for a current download URL.

The following detail fields are illustrative; every value is fictitious:

json
{
  "object": "asset",
  "asset_id": "asset_example",
  "asset_ref": "Asset://asset_example",
  "group_id": "group_example",
  "upstream": {
    "asset_id": "asset-official-example",
    "group_id": "group-official-example",
    "url": "https://media.example.test/reference.png",
    "status": "Active"
  }
}
FieldMeaning
Top-level asset_id, asset_ref, group_idMoonApiX identifiers; continue using these in generation and management requests.
upstream.asset_idThe original Volcengine asset ID returned by official detail.
upstream.group_idThe registered official group ID used for ID resolution.
upstream.observed_group_idOptional. If detail reports a different official group ID, this preserves the observation without replacing the registered value. Read this field when present, otherwise upstream.group_id, to obtain the group observed in this detail.
upstream.urlThe HTTPS media download URL from official detail. It may be absent while processing or before an address is available.
upstream.statusOfficial asset state; it does not replace the same model's preparation.generation_ready check.

Download and refresh URLs ​

upstream.url may contain a short-lived download signature, commonly valid for approximately 12 hours. It is not a permanent address. GET the same asset's /upstream?model=... detail when downloading, preserve every query parameter and use the newly returned address. Refreshing does not require another upload or asset creation. ID resolution and lists do not return refreshed download URLs.

Some addresses reject HEAD with 403 while allowing anonymous Range GET 206 and full GET 200. If HEAD is rejected, verify a GET with Range: bytes=0-0, checking both the HTTP status and matching media Content-Type. If GET also fails, read detail again for the current URL and recheck it. Media downloads do not require a MoonApiX API key; never send the API key to the media domain. After downloading, verify file size, media specifications and SHA-256 as needed.

The following Python 3 example uses the detail receipt above to verify and download an image anonymously. It does not put the full URL in command arguments or output:

bash
python3 - "$RECEIPTS" <<'PY'
import hashlib
import json
from pathlib import Path
import sys
import urllib.request
from urllib.parse import urlparse

receipts = Path(sys.argv[1])
detail = json.loads((receipts / "detail.json").read_text())
url = detail["upstream"]["url"]
assert urlparse(url).scheme == "https"
request = urllib.request.Request(url, headers={"Range": "bytes=0-0"})
with urllib.request.urlopen(request, timeout=60) as response:
    assert response.status in (200, 206)
    assert response.headers.get_content_type() == "image/png"
    response.read(1)
digest = hashlib.sha256()
with urllib.request.urlopen(url, timeout=120) as response:
    assert response.status == 200
    assert response.headers.get_content_type() == "image/png"
    with (receipts / "reference-downloaded.png").open("wb") as output:
        while chunk := response.read(1024 * 1024):
            output.write(chunk)
            digest.update(chunk)
print("Downloaded image/png; SHA-256:", digest.hexdigest())
PY

List and rename assets ​

bash
curl --fail-with-body "https://moonapix.com/v1/assets/upstream?model=seedance-2.0-sz&offset=0&limit=20" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY"

curl --fail-with-body -X PATCH https://moonapix.com/v1/assets/asset_example/upstream \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"seedance-2.0-sz","name":"product-reference-updated"}'

Lists include only resources registered to the authenticated account in the selected model's asset scope. Pagination uses offset / limit, defaulting to 0 / 20, with a limit of 1..100. Lists are summaries and do not provide current download URLs. Renaming accepts only name, which must not be blank and must be at most 64 characters; description is not accepted. Detail may show the old name for several minutes after a successful update. Query the same resource at intervals to confirm it.

Official ID resolution ​

POST /v1/assets/resolve requires model and at least one of upstream_asset_id or upstream_group_id. When both are supplied, they must belong to the same MoonApiX group. A successful group-only result contains empty asset_id and asset_ref strings; an asset result supplies the identifiers usable for generation.

Only valid resources registered to the authenticated account in the same target model asset scope can be resolved. Unknown, invalidated, cross-account or out-of-scope IDs return 404; ambiguous resources return 409. This endpoint does not import arbitrary official resources. If detail reported a different upstream.observed_group_id, continue using the registered upstream.group_id for resolution. Generation cannot use an official ID as a MoonApiX asset_id or Asset://asset_xxx; resolve it first and use the top-level MoonApiX identifiers.

Clean up resources from the example ​

These commands continue the complete example and delete only the resources created there. Delete the official asset first, then its empty official group, then soft-delete the MoonApiX asset and group. Keep each receipt until the corresponding operation succeeds.

bash
curl --silent --show-error --fail-with-body -X DELETE \
  "$BASE/v1/assets/$ASSET_ID/upstream?model=$MODEL" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -o "$RECEIPTS/delete-official-asset.json"
curl --silent --show-error --fail-with-body -X DELETE \
  "$BASE/v1/asset-groups/$GROUP_ID/upstream?model=$MODEL" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -o "$RECEIPTS/delete-official-group.json"
curl --silent --show-error --fail-with-body -X DELETE "$BASE/v1/assets/$ASSET_ID" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -o "$RECEIPTS/delete-asset.json"
curl --silent --show-error --fail-with-body -X DELETE "$BASE/v1/asset-groups/$GROUP_ID" \
  -H "Authorization: Bearer $MOONAPIX_API_KEY" -o "$RECEIPTS/delete-group.json"

Official deletion returns HTTP 200 with deleted=true. Resources being prepared or deleted, shared resources and nonempty official groups may return 409. An uncertain official deletion can be retried on the same subresource; local soft deletion alone does not prove official deletion. An official resource can no longer be resolved after deletion. Deletion does not guarantee immediate expiry of issued media URLs or cancel generation tasks already submitted.