Skip to content

Assets

The asset API stores reference material — images, videos, audio — that Seedance video models can take as input. You add the material once, wait for the upstream asset service to process it, and then refer to it as asset://<asset_id> in a video generation request instead of passing the same public URL every time.

This is a management API, not a generation API. RouteAPI authenticates the call with your API Key, forwards it to the upstream asset service, and returns the upstream response. Calls on these endpoints are not billed as model usage. They are also outside the OpenAI SDK surface, so use plain HTTP rather than an SDK client.

  1. Create an asset group with POST /v1/asset-groups.
  2. Add an asset to that group with POST /v1/assets, passing a publicly downloadable URL.
  3. Poll the asset with GET /v1/assets/{assetID} until its status is Active. Processing is asynchronous.
  4. Reference the asset as asset://<asset_id> in the metadata.content of a POST /v1/video/generations request. See Using an asset in a Seedance request and Seedance.
  5. Poll the video task as usual. Asset-backed video requests are sent to the upstream once; they are not retried or failed over to another channel.
MethodPathPurposeAllocates asset space
POST/v1/asset-groupsCreate an asset groupYes
GET/v1/asset-groupsList groupsNo
GET/v1/asset-groups/{groupID}Retrieve one groupNo
DELETE/v1/asset-groups/{groupID}Delete a groupNo
PUT/v1/asset-groups/{groupID}Rename a groupNo
POST/v1/assetsAdd an asset to a groupYes
GET/v1/assetsList assets in a groupNo
GET/v1/assets/{assetID}Retrieve one asset (use this to poll)No
DELETE/v1/assets/{assetID}Delete an assetNo
PUT/v1/assets/{assetID}Rename an assetNo
POST/v1/assets/verify-sessionsCreate a human verification sessionYes
GET/v1/assets/verify-sessions/{sessionID}Retrieve a verification sessionNo

Send a Model API Key in the standard header:

Authorization: Bearer sk-your-routeapi-key

The Key identifies the RouteAPI account. Assets are isolated per account: every API Key under the same account sees the same groups, assets, and verification sessions, and no other account can see them. The Key is never sent upstream — RouteAPI uses a separate upstream credential on your behalf.

Each account is bound to one upstream credential, which the asset service uses to keep that account’s material separate. It is assigned automatically the first time the account calls a creating endpoint:

  • POST /v1/asset-groups
  • POST /v1/assets
  • POST /v1/assets/verify-sessions

Once assigned, the binding is permanent for the account and is reused by every later call, including video requests that reference asset:// assets.

Read and delete endpoints never allocate. Before the binding exists:

CallResponse
GET /v1/asset-groups200, empty list: {"total": 0, "asset_groups": []}
Any other GET or DELETE404, {"error": "not found"}

That is, an empty group list is a normal answer for a new account, while a 404 on a specific asset or group before first use means “this account has no asset space yet”, not “the ID is wrong”.

Group-to-credential routing is a service configuration detail. If your group has no asset channel configured, the first creating call fails with 503 asset_pool_exhausted or 503 asset_pool_misconfigured instead of allocating. Retry later, or contact support if it persists.

POST /v1/asset-groups
Terminal window
curl https://api.routeapi.ai/v1/asset-groups \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "product-refs",
"description": "Reference material for product videos"
}'
FieldTypeRequiredDescription
namestringYesGroup name
descriptionstringNoGroup description

Response:

{
"asset_group_id": "local_group_2c8186e6d8b44ffa73b371677bfe4af2",
"name": "product-refs",
"project_name": "default",
"status": "active"
}

Keep asset_group_id: it is required when adding assets.

GET /v1/asset-groups
Terminal window
curl "https://api.routeapi.ai/v1/asset-groups?page=1&page_size=20" \
-H "Authorization: Bearer $ROUTEAPI_KEY"

Pagination parameters such as page and page_size are forwarded as-is; the upstream service’s defaults and maximum page size apply.

{
"total": 1,
"asset_groups": [
{
"asset_group_id": "local_group_2c8186e6d8b44ffa73b371677bfe4af2",
"group_type": "AIGC",
"project_name": "default",
"session_id": "",
"status": "active",
"created_at": 1792050332,
"updated_at": 1792050332
}
]
}

session_id is only set for groups created through human verification. A verified-person group has group_type LivenessFace; ordinary groups are AIGC.

Terminal window
curl https://api.routeapi.ai/v1/asset-groups/local_group_2c8186e6d8b44ffa73b371677bfe4af2 \
-H "Authorization: Bearer $ROUTEAPI_KEY"

The upstream detail response may return an empty status string. Use the value from the list response or the create response when you need to decide whether a group is usable.

Terminal window
curl -X DELETE https://api.routeapi.ai/v1/asset-groups/local_group_2c8186e6d8b44ffa73b371677bfe4af2 \
-H "Authorization: Bearer $ROUTEAPI_KEY"
{ "deleted": "local_group_2c8186e6d8b44ffa73b371677bfe4af2" }

Deletion is permanent.

Terminal window
curl -X PUT https://api.routeapi.ai/v1/asset-groups/local_group_2c8186e6d8b44ffa73b371677bfe4af2 \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "new_group_name"}'
{ "name": "new_group_name", "updated": "local_group_2c8186e6d8b44ffa73b371677bfe4af2" }
POST /v1/assets

One asset per request. Pass the group, a name, and exactly one source URL.

FieldTypeRequiredDescription
asset_group_idstringYesGroup the asset belongs to
asset_namestringYesName used for lookup only; it does not affect generation
image_urlstringOne of threePublic URL of an image
video_urlstringOne of threePublic URL of a video
audio_urlstringOne of threePublic URL of an audio file
asset_typestringNoImage, Video, or Audio; inferred from the URL field when omitted
descriptionstringNoAsset description

Pass exactly one of image_url, video_url, and audio_url; the upstream service rejects requests with zero or several. The URL must be downloadable from the public internet without authentication or extra headers. Base64 payloads and internal addresses are not supported, and the URL must stay reachable until processing finishes.

Terminal window
curl https://api.routeapi.ai/v1/assets \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"asset_group_id": "local_group_2c8186e6d8b44ffa73b371677bfe4af2",
"asset_name": "product-front",
"image_url": "https://cdn.example.com/product-front.jpg"
}'
{
"asset_id": "local_asset_25e4d0cb018fb5802bf4e466f99707d0",
"asset_group_id": "local_group_2c8186e6d8b44ffa73b371677bfe4af2",
"asset_name": "product-front",
"asset_type": "Image",
"asset_uri": "asset://local_asset_25e4d0cb018fb5802bf4e466f99707d0",
"status": "Processing"
}

A successful create means the request was accepted, not that the asset is usable. asset_uri is the form to use in video requests.

StatusMeaningWhat to do
ProcessingStill being ingestedKeep polling
ActiveReady to useReference it in a video request
FailedIngestion failedCheck format and size requirements, then add it again

Use this endpoint for polling, every 5–10 seconds, with an overall timeout. Processing usually takes seconds to tens of seconds; video assets and busy periods take longer.

Terminal window
curl https://api.routeapi.ai/v1/assets/local_asset_25e4d0cb018fb5802bf4e466f99707d0 \
-H "Authorization: Bearer $ROUTEAPI_KEY"
{
"asset_id": "local_asset_25e4d0cb018fb5802bf4e466f99707d0",
"asset_group_id": "local_group_2c8186e6d8b44ffa73b371677bfe4af2",
"status": "Active",
"asset_uri": "asset://local_asset_25e4d0cb018fb5802bf4e466f99707d0",
"url": "https://..."
}

url is a temporary signed download address. Do not store it; use asset_uri for generation.

A missing or deleted asset returns 404 from the upstream service, for example:

{ "details": "record not found", "error": "asset not found in database" }
Terminal window
# List — asset_group_id is required
curl "https://api.routeapi.ai/v1/assets?asset_group_id=local_group_2c8186e6d8b44ffa73b371677bfe4af2" \
-H "Authorization: Bearer $ROUTEAPI_KEY"
# Delete
curl -X DELETE https://api.routeapi.ai/v1/assets/local_asset_25e4d0cb018fb5802bf4e466f99707d0 \
-H "Authorization: Bearer $ROUTEAPI_KEY"
{ "deleted": "local_asset_25e4d0cb018fb5802bf4e466f99707d0" }

Deleting an asset breaks any new video request that references it.

Terminal window
curl -X PUT https://api.routeapi.ai/v1/assets/local_asset_25e4d0cb018fb5802bf4e466f99707d0 \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "new_asset_name"}'
{ "updated": "local_asset_25e4d0cb018fb5802bf4e466f99707d0" }

After the rename, asset detail and list responses return the new name.

Reference material that contains a real person’s face needs the person’s own consent first. A verification session produces a dedicated group for that person’s material; assets added to it are checked for face consistency.

POST /v1/assets/verify-sessions

The body may be empty ({} or no body at all). callback_url is accepted but not used for redirection.

Terminal window
curl https://api.routeapi.ai/v1/assets/verify-sessions \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{}'
{
"session_id": "asset_sess_5e7860e76613dd51a4867bdcbea6076b",
"status": "pending",
"h5_link": "https://...one-time-link...",
"callback_url": ""
}

Send h5_link only to the person who will be verified. It contains a one-time credential and grants access to the verification page; do not put it in public logs, screenshots, or group chats.

Terminal window
curl https://api.routeapi.ai/v1/assets/verify-sessions/asset_sess_5e7860e76613dd51a4867bdcbea6076b \
-H "Authorization: Bearer $ROUTEAPI_KEY"
statusMeaning
pendingWaiting for the person, or still being processed
succeededVerified; asset_group_id is populated
failedVerification failed; read error
{
"session_id": "asset_sess_a8af6fab34f694ada40c22521448f579",
"status": "succeeded",
"result_code": "",
"error": "",
"asset_group_id": "group-20261015202316-4t5jt",
"project_name": "default",
"created_at": 1792066965,
"updated_at": 1792067203
}

There is no webhook or redirect back to your service — the person’s browser ends on a result page owned by the upstream. Poll the session until status becomes succeeded (usually within minutes), then add that person’s material to the returned asset_group_id. result_code is currently always empty; use status. A failed session with error such as verification expired has to be recreated.

Reference the asset inside metadata.content of a video generation request, using the asset_uri returned by the asset API:

Terminal window
curl https://api.routeapi.ai/v1/video/generations \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.0-fast",
"prompt": "The product in image 1 rotates slowly",
"metadata": {
"content": [
{ "type": "text", "text": "The product in image 1 rotates slowly" },
{
"type": "image_url",
"image_url": { "url": "asset://local_asset_25e4d0cb018fb5802bf4e466f99707d0" },
"role": "reference_image"
}
],
"duration": 4,
"resolution": "480p",
"ratio": "1:1"
}
}'

Use a Seedance model ID from GET /v1/models that supports video generation; see Models. The request format for Seedance, including metadata.content roles and model-specific limits, is documented in Seedance.

Rules that come from the asset service contract:

  • The type/role pair must match the asset type: image_url with reference_image, video_url with reference_video, audio_url with reference_audio.
  • Only Active assets can be referenced.
  • Prompt text refers to material by ordinal (“image 1”, “video 1”), counted within each type in content order. Do not put asset IDs in the prompt.
  • Real-person material must come from a verified group; otherwise the upstream may reject or fail the task.

RouteAPI detects a reference by looking for any JSON string value that starts with asset:// (case-insensitive). Such a request is pinned to your asset channel and sent once, with no retry or channel failover. The request is rejected locally in these cases:

StatusCodeCause
400asset_key_requiredThe body references asset://, but the account has no asset space yet. Create a group and an asset first
400asset_remix_conflictA remix of a non-asset task tries to reference an asset:// asset
400asset_channel_conflictThe API Key is pinned to a specific channel that is not your asset channel
400asset_model_unsupportedThe model is not supported by your asset channel
503asset_key_unavailableThe upstream credential bound to the account is faulted or disabled

These errors use the task error shape {"code": ..., "message": ..., "data": ...}.

  • Request body — POST and PUT bodies are read with a 1 MiB limit. A larger body returns 413 before anything is sent upstream. Material itself is never uploaded through the body; pass a URL.
  • Body shape — a non-empty POST or PUT body must be a JSON object. Arrays, bare strings, null, and invalid JSON return 400 without reaching the upstream service. GET and DELETE bodies are ignored.
  • Rename body — a PUT body must contain a non-empty string field name (lowercase key). A missing, empty, or non-string name returns 400 without reaching the upstream service.
  • Response body — upstream responses over 10 MiB, or responses that fail to read, return 502.
  • Timeout — the upstream call has a fixed 60-second timeout; expiration is reported as 502.
  • project_name — if present in a POST or PUT body, this field is removed before forwarding, in any letter case.
  • Ownership errors — if the upstream answers 403 with an ownership denial (“you do not own” / “access denied”), RouteAPI returns 404 {"error": "not found"} instead, so the resource looks nonexistent rather than forbidden.
  • Upstream credential failures — an upstream 401 becomes 503 asset_key_unavailable and marks the account’s binding as faulted (see above). An upstream 402, or an upstream error whose body reads like a billing problem, becomes 503 asset_service_unavailable.

Other upstream status codes and bodies are passed through unchanged, including upstream validation errors. RouteAPI’s own errors use one of three shapes:

{ "error": "not found" }
{ "error": "<message>", "code": "asset_pool_busy" }
{
"error": {
"message": "token.invalid (request id: ...)",
"type": "error",
"code": ""
}
}

The third shape comes from the shared authentication and rate-limit middleware, so check whether error is a string or an object. Local message text is currently Chinese — for the first two shapes, branch on the HTTP status and the code field rather than on the message.

StatusCodeMeaningHandling
400—Body is not a JSON object, or the upstream rejected the parametersFix the request; do not retry unchanged
401—Missing, invalid, or deleted API KeyCheck Authorization
404—Unknown asset, group, or session; or no asset space yet for this accountCheck the ID; create a group first if the account is new
413—Request body exceeds 1 MiBSend a URL instead of inline data
429—Rate limit triggeredSlow down polling and retry later
500asset_pool_internal_errorRouteAPI could not read or write its own asset stateRetry later
500—RouteAPI failed while reading the account’s asset stateRetry later
502—Upstream unreachable, timed out, or returned an unreadable or oversized responseRetry later
503asset_key_unavailableThe account’s upstream credential is faulted or disabledContact support; retrying will not help
503asset_pool_exhaustedYour group has no asset channel, or no credential is available to allocateRetry later or contact support
503asset_pool_busyCredentials exist but none could be allocated right nowRetry later
503asset_pool_misconfiguredYour group maps to more than one asset channelContact support
503asset_channel_unavailableThe asset channel itself is disabled or misconfiguredRetry later or contact support
503asset_service_unavailableUpstream account balance or hard failureRetry later or contact support

Asset calls have no model cost, but when the platform’s model request rate limit is enabled they count against it like any other /v1 request. See Common Errors for the general error model, and Model Pricing for how the video generation that consumes these assets is billed.

  • Seedance — Seedance request format and metadata.content roles.
  • Create a video task — the shared request envelope for every video model.
  • Models — find a supported video model ID.
  • Common Errors — status codes and troubleshooting order.