Seedance real-person asset API
Create a Seedance real-person asset for a real, identifiable person. Your server opens a verification session, the person gives consent and completes a liveness check on Volcengine's official page, and you then create the asset from their photo to get an asset:// URI for video generation. You do not sign in to YingTu, but the person must complete the check themselves.
Last updated: 2026-09-25 · checked line by line against the live API code
Overview#
When a video has to show a real person (a host, spokesperson, or instructor), Seedance requires that person's own authorization; an ordinary photo is not enough. This API wraps the authorization into three steps: create a verification session, let the person verify, then create the asset.
The verification page is run by Volcengine (ByteDance's cloud platform) on ark.volcengine.com, and the person needs a device with a camera for the liveness check. YingTu never sees the person's ID documents or face data; it only relays the result.
The person must complete the verification themselves; nobody can do it on their behalf. For AI-generated or fictional characters, use the simpler virtual-human asset API.
Before you start#
- A LaoZhang API key. The asset API only checks that the key is valid and accepts any group, but generating videos with the asset requires a SeeDance2-group key. Use the same SeeDance2 key throughout, because
verification_idand assetidvalues can only be queried with the key that created them. - A server of your own. Browsers cannot call the API directly, and the key must stay out of front-end code.
- A callback page (
callback_url) at a public HTTPS URL. The person's browser lands there after verification. It is a web page for the person, not a webhook for your server. - A way to hand the verification link to the person, for example opening it in your app or website, or sending it by SMS or email.
- A public HTTPS URL of a clear, front-facing photo of the person, plus persistent storage for
verification_id,id, anduri.
How it works#
- 1
Your server
Calls
POST /api/seedance-assets/real-persons/verificationswith acallback_url, stores the returnedverification_idnext to your user, and hands theverification_urlto the person. - 2
The person
Opens
verification_urlwithin 30 minutes, gives consent, and completes the liveness check on Volcengine's official page. - 3
Volcengine → YingTu → the person's browser
When the flow ends, the person's browser goes to YingTu's relay URL and is redirected (302) to your
callback_urlwithverification_idandresult_codeappended. - 4
Your server
When the callback page is opened (or on a timer), calls
GET /api/seedance-assets/real-persons/verifications/{verification_id}untilstatusisverified. - 5
Your server
Before
expires_at, callsPOST /api/seedance-assets/real-personswith a clear, front-facing photo of the person and receives the assetidanduri(statusProcessing). - 6
Your server
Polls
GET /api/seedance-assets/real-persons/{id}untilActive, then passes theurias areference_imageto video generation.
The callback is a browser redirect, not a server notification. YingTu never sends webhooks to your server. If the person closes the page after verifying, your callback page never opens, so you must be able to get the result by polling the GET endpoint.
Basics#
| Item | Value | Notes |
|---|---|---|
| Base URL | https://yingtu.ai | All paths below are relative to this origin. |
| Authentication | Authorization: Bearer $LAOZHANG_API_KEY | Bearer is case-sensitive and must be followed by one space and the full key. A missing or malformed header returns 401 invalid_api_key. |
| Request body | Content-Type: application/json | POST only. The body must be a JSON object of at most 16 KB (16,384 bytes) and may contain only the documented fields. |
| Responses | JSON | Every success and error response includes request_id, also sent as the X-Request-Id header. All responses carry Cache-Control: no-store. |
| Where to call from | Your server only | The API sends no CORS headers, so browsers block direct calls, and your key must never ship to a browser anyway. |
| Timestamps | ISO 8601 | expires_at is in UTC, for example 2026-09-25T08:30:00.000Z. |
YingTu validates your key by calling LaoZhang's /v1/models with it and caches a successful result for 5 minutes per server instance. A key you just disabled can therefore keep working for up to 5 minutes. If LaoZhang's validation service is unreachable you get 503 authentication_unavailable; retry later.
Endpoints#
Create a verification session
Creates a verification session for one real person. Every call creates a new session and uses one write. Give verification_url to the person; keep verification_id on your server.
curl -X POST https://yingtu.ai/api/seedance-assets/real-persons/verifications \
-H "Authorization: Bearer $LAOZHANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_url": "https://app.example.com/seedance/verified?user=42",
"language": "en"
}'| Field | Type | Required | Description |
|---|---|---|---|
callback_url | string | Yes | Your page that the person's browser is sent to after verification. Must be a public HTTPS URL, with the same rules as image_url (see Create a real-person asset). Your own query parameters (such as a user or order ID) are kept on redirect, but parameters named verification_id or result_code are overwritten. |
language | string | No | Language of the verification page: zh (Simplified Chinese, default), en, or zh-Hant. Case-sensitive; any other value returns 400. Volcengine offers only these three, so use en for Russian, Japanese, Korean, or Spanish speakers. |
The body may contain only these two fields.
HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
X-RateLimit-Limit: 12
X-RateLimit-Remaining: 11
X-RateLimit-Reset: 1790326800
{
"request_id": "req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a",
"verification_id": "ver_Hq3nW8bYt5...Kd0sE",
"status": "requires_user_action",
"verification_url": "https://ark.volcengine.com/...&lng=en",
"callback_url": "https://app.example.com/seedance/verified?user=42",
"expires_at": "2026-09-25T08:30:00.000Z"
}| Field | Type | Description |
|---|---|---|
request_id | string | Trace ID for this request. |
verification_id | string | Session ID starting with ver_. It is an encrypted token (usually 300+ characters, length varies) bound to your key. Keep it on your server; you need it to check the result and to create the asset. |
status | string | Always requires_user_action: waiting for the person to verify. |
verification_url | string | Volcengine's official verification page (https://ark.volcengine.com/...) with lng set from language. Give it only to the person; do not log it or put it on a public page. |
callback_url | string | The callback URL you sent, after URL normalization. |
expires_at | string | When verification_id expires (UTC, 30 minutes after creation). After that, checking status and creating the asset both return 410. |
Check verification status
Tells you whether the person has passed. Every call asks Volcengine live and does not count toward the write quota. Check once when your callback page opens; if it is not verified yet, poll about every 5 seconds until verified or expiry.
# VERIFICATION_ID is the verification_id (ver_...) you stored
curl https://yingtu.ai/api/seedance-assets/real-persons/verifications/$VERIFICATION_ID \
-H "Authorization: Bearer $LAOZHANG_API_KEY"| Path parameter | Type | Required | Description |
|---|---|---|---|
verification_id | string | Yes | The verification_id from the create response, or the one appended to your callback URL. Either works. |
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
{
"request_id": "req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a",
"verification_id": "ver_Hq3nW8bYt5...Kd0sE",
"status": "pending",
"expires_at": "2026-09-25T08:30:00.000Z"
}HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
{
"request_id": "req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a",
"verification_id": "ver_Hq3nW8bYt5...Kd0sE",
"status": "verified",
"expires_at": "2026-09-25T08:30:00.000Z"
}| Field | Type | Description |
|---|---|---|
request_id | string | Trace ID for this request. |
verification_id | string | Echoes the value you sent. |
status | string | pending or verified. See Statuses. |
expires_at | string | When this verification_id expires (UTC). |
pending comes back with HTTP 200; it is not an error. After expiry this endpoint returns 410 verification_expired instead of a status.
Create a real-person asset
After verification passes, creates the real-person asset from the person's photo. Call it before verification_id expires. It returns 202, the asset is processed in the background, and you check its status just like a virtual-human asset. Every call uses one write.
# VERIFICATION_ID is the verification_id (ver_...) you stored
curl -X POST https://yingtu.ai/api/seedance-assets/real-persons \
-H "Authorization: Bearer $LAOZHANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"verification_id": "'"$VERIFICATION_ID"'",
"image_url": "https://cdn.example.com/people/presenter-a.jpg",
"name": "presenter-a"
}'| Field | Type | Required | Description |
|---|---|---|---|
verification_id | string | Yes | A session that is already verified, created with the same key. |
image_url | string | Yes | Public HTTPS URL of a clear, front-facing photo of the person who completed the verification. |
name | string | No | Asset name, 1–64 characters after trimming. Omit it if you do not need it; an empty string or null returns 400. |
The body may contain only these three fields. YingTu checks in this order: body format → image_url → whether the verification_id belongs to you (403) → expiry (410) → whether it has passed (409) → create the asset.
URL rules (apply to both image_url and callback_url)
- Must start with
https://and must not contain a username or password (https://user:pass@...). Leading and trailing spaces are trimmed. - The host cannot be
localhost,*.localhost, or*.local, and cannot be a private or reserved IP:0.x,10.x,127.x,169.254.x,172.16.x–172.31.x,192.168.x, or the IPv6 addresses::,::1,::ffff:*,fc00::/7,fe80::/10. - YingTu only checks the URL format; it never downloads the image. Volcengine does. The URL therefore has to be downloadable anonymously from the public internet: no hotlink protection, no login, no cookies. If you use a pre-signed URL (S3, GCS, OSS, and so on), keep it valid until the asset reaches
ActiveorFailed. - Problems with the image itself (content, format, size) usually surface later as
status: "Failed". Occasionally Volcengine rejects the request at creation time, which returns 502upstream_error.
HTTP/1.1 202 Accepted
Content-Type: application/json
Cache-Control: no-store
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
X-RateLimit-Limit: 12
X-RateLimit-Remaining: 10
X-RateLimit-Reset: 1790326800
{
"request_id": "req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a",
"id": "ast_9Qk2vX7mL0pT...cRz4w",
"object": "seedance.asset",
"kind": "real_person",
"name": "presenter-a",
"status": "Processing",
"uri": "asset://asset-20260925080012-x7k2p"
}The id in the example is shortened. Response fields:
| Field | Type | Description |
|---|---|---|
request_id | string | Trace ID for this request: req_ followed by 32 hex characters. Include it when you contact support. |
id | string | YingTu asset ID, starting with ast_. Use it only to check status. It is an encrypted token (usually around 250 characters, length varies) bound to the API key that created it. Store it as-is; do not truncate or parse it. |
object | string | Always seedance.asset. |
kind | string | Always real_person. |
name | string | Asset name. Empty string when there is none. |
status | string | Asset status, passed through from Volcengine: Processing, Active, or Failed. See Statuses. |
uri | string | The value you pass to video generation, in the form asset://asset-.... It never changes after creation. Use it exactly as returned. |
created_at | string | Optional. Creation time reported by Volcengine; omitted when Volcengine does not return it. |
updated_at | string | Optional. Last update time reported by Volcengine. |
failure | object | Optional. Present only when Volcengine reports an error: { "code": "...", "message": "..." }. Either field may be an empty string. |
Retrieve a real-person asset
Returns the asset's current status. Every call asks Volcengine live and does not count toward the write quota. Only the key that created the asset can query it. Asset IDs do not expire; an expired verification session does not affect assets already created from it.
# ASSET_ID is the id (ast_...) returned by the create call
curl https://yingtu.ai/api/seedance-assets/real-persons/$ASSET_ID \
-H "Authorization: Bearer $LAOZHANG_API_KEY"| Path parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The id (ast_...) returned when the asset was created. Passing a verification_id, the uri, or asset-... returns 403. |
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
{
"request_id": "req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a",
"id": "ast_9Qk2vX7mL0pT...cRz4w",
"object": "seedance.asset",
"kind": "real_person",
"name": "presenter-a",
"status": "Active",
"uri": "asset://asset-20260925080012-x7k2p",
"created_at": "2026-09-25T08:00:12Z",
"updated_at": "2026-09-25T08:01:03Z"
}A failed asset still returns HTTP 200, with status: "Failed" and a failure object. Check the status field, not just the HTTP status.
Statuses#
Verification session
| Status | Where you see it | Meaning | What to do |
|---|---|---|---|
requires_user_action | Only in the create-session response | The session exists and is waiting for the person to open the link. | Hand verification_url to the person. |
pending | GET status check | No passing result yet. Not opened, in progress, abandoned, and not passed all show as pending. | Keep polling until expires_at. If it never passes, create a new session so the person can try again. |
verified | GET status check | The person passed. | Call POST /real-persons before expires_at. |
| HTTP 410 | GET status check and asset creation | The verification_id expired (verification_expired). | Create a new session. Assets already created are not affected. |
There is no "failed" verification status. If the person does not pass, the status stays pending until it expires. You can use the callback's result_code to warn the person earlier, but whether they passed is decided only by the GET endpoint returning verified.
Asset
| status | Meaning | What to do |
|---|---|---|
Processing | Volcengine is processing the photo. The create response usually shows this. | Poll every 5–10 seconds. Not usable for generation yet. |
Active | Processing finished; ready for video generation. | Stop polling and start generating with the uri. |
Failed | Processing failed; failure holds Volcengine's reason. | Fix the cause and create the asset again while the verification is still valid; after expiry the person has to verify again. |
Asset status is passed through from Volcengine unchanged. Treat any value not listed here as not ready and keep polling.
Verification callback#
When the session is created, YingTu gives Volcengine its own relay URL, not your callback_url. When the person finishes, Volcengine sends their browser to the relay URL, and YingTu redirects the browser (302) to your callback_url:
# 1) Volcengine sends the person's browser to YingTu's relay URL
GET https://yingtu.ai/api/seedance-assets/real-persons/callback/cb_...?bytedToken=...&resultCode=10000
# 2) YingTu answers with a redirect to your callback_url
HTTP/1.1 302 Found
Location: https://app.example.com/seedance/verified?user=42&verification_id=ver_Zp7cM1xRa4...Ty9vB&result_code=10000| Appended parameter | Description |
|---|---|
verification_id | A freshly issued session ID, also bound to your key. Normally it points to the same verification as the one you received at creation and works for status checks and asset creation. The string differs from the original (each encryption is different), so never compare the two as strings. It is only appended when Volcengine returns its session token. |
result_code | Volcengine's result code, passed through unchanged; YingTu does not interpret it. Only appended when Volcengine returns one. |
- Treat the callback as a signal that the person is back. Anyone can forge the query string, so never decide pass or fail from
result_code; always confirmverifiedwith the GET endpoint. - Check status with the
verification_idyou stored when creating the session, and use your owncallback_urlparameters (such asuser=42) to find the matching record. - Volcengine's session token (
bytedToken) never appears in the redirected URL. - The relay URL stops working 35 minutes after the session was created. After that the person sees HTTP 400 with
{"error":"Invalid or expired verification callback."}and is not redirected to your page. - The callback page is for the person. Show something like "Verification submitted, confirming…" and let your server check the result.
Errors#
Every error uses the same shape. Look at both the HTTP status and error.code. error.retryable: true means an identical retry may succeed; false means you need to change the request or contact support. This example is a real response:
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
X-Request-Id: req_42480b1cfd2147439b8c514ad8688ab0
{
"error": {
"code": "invalid_request",
"message": "Unknown request field: consent_reference.",
"request_id": "req_42480b1cfd2147439b8c514ad8688ab0",
"retryable": false
}
}| HTTP | code | When it happens | What to do | Retryable |
|---|---|---|---|---|
| 400 | invalid_request | The body is not a JSON object, a required field is missing, a value is invalid, or the body contains an undocumented field. message names the field. | Fix the request as message describes. Retrying unchanged will not help. | No |
| 401 | invalid_api_key | No Authorization header, not in Bearer <key> form, or LaoZhang reports the key as invalid or disabled. | Check the key and the header format. | No |
| 403 | resource_not_owned | The id or verification_id was not created with this key, was truncated or altered, or a virtual-human id was sent to a real-person path. Unknown and not-yours both return 403; the API never returns 404 here. | Use the same key that created it, the right path, and the ID exactly as returned. | No |
| 403 | capability_unavailable | The asset capability is unavailable on the Volcengine side. This is a problem with YingTu's service account, not your key. | Contact support with the request_id. | No |
| 409 | verification_pending | POST /real-persons was called with a verification_id that has not passed yet. | Poll the GET endpoint until verified, then create. Do not retry the POST in a loop: every POST uses write quota. | Yes |
| 410 | verification_expired | The verification_id is past its expires_at. Returned by both the status check and asset creation. | Create a new verification session and have the person verify again. | No |
| 413 | payload_too_large | The body is larger than 16 KB. | Shrink the body. Valid requests are far below the limit. | No |
| 415 | unsupported_media_type | A POST was sent without Content-Type: application/json. | Add Content-Type: application/json. | No |
| 429 | rate_limit_exceeded | The key exceeded 12 writes per hour, or Volcengine throttled the request. | If Retry-After is present, wait that many seconds. Without it (upstream throttling), back off exponentially. | Yes |
| 500 | internal_error | Unexpected error inside YingTu. | Contact support with the request_id. A POST may have partly run, so a retry can create a duplicate. | No |
| 502 | upstream_error | Volcengine returned an error or incomplete data (for example no asset ID, or a verification link that failed YingTu's safety checks). | Follow retryable: if true, retry later; if false, Volcengine usually rejected the request content, so change it first. | Varies |
| 503 | authentication_unavailable | LaoZhang key validation is temporarily unreachable. | Retry later. | Yes |
| 503 | service_unavailable | YingTu's server configuration is temporarily unavailable. | Retry later; contact support if it persists. | Yes |
Two responses do not use this JSON shape: a wrong HTTP method (for example GET on a create endpoint) returns 405 with no body, and an expired real-person callback relay shows the person's browser a 400 (see Verification callback).
Rate limits and tracing#
- Each API key gets at most 12 write requests per hour, counted across all POST endpoints together: creating virtual-human assets, creating real-person verification sessions, and creating real-person assets. GET requests are not counted.
- Every POST that passes key validation counts, including requests rejected with 400, 403, 409, 410, 413, or 415 and requests that fail upstream. 401 responses do not count. Validate parameters on your side before sending.
- The window starts with the first POST in the hour and resets completely one hour later. It is not a sliding window.
- Successful POST responses include
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset(window reset time in Unix seconds). Error responses do not include these headers. - Over the limit you get 429
rate_limit_exceededwithRetry-Afterin seconds. Throttling by Volcengine also returns 429, but withoutRetry-After. - Each real-person asset needs at least two POSTs (verification session + asset), so one key can create about 6 real-person assets per hour. Poll the GET endpoint while waiting for verification instead of retrying POSTs.
- If this quota is too low for your volume, contact YingTu support.
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
X-RateLimit-Limit: 12
X-RateLimit-Remaining: 11
X-RateLimit-Reset: 1790326800Using the asset in Seedance video generation#
Videos are not generated by the YingTu asset API. Call LaoZhang's Seedance endpoint POST https://api.laozhang.ai/seedance/api/v3/contents/generations/tasks instead, with a key from the SeeDance2 group.
- Add an
image_urlitem to thecontentarray: seturlto the asset'suri(asset://...) androletoreference_image. - Only use assets whose status is
Active. Do not submit assets that are stillProcessingor haveFailed. - Reuse the same
uriin as many tasks as you like; there is no need to create the asset again. - Reference images per task are capped: up to 9 for the Seedance 2.0 models and up to 30 for Seedance 2.5.
- For every other parameter (model, ratio, duration, resolution, polling the task), follow the LaoZhang Seedance docs.
curl -X POST https://api.laozhang.ai/seedance/api/v3/contents/generations/tasks \
-H "Authorization: Bearer $LAOZHANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "doubao-seedance-2-0-fast-260128",
"content": [
{ "type": "text", "text": "The character from the reference image walks slowly toward the camera on a rainy neon street at night, cinematic lighting" },
{
"type": "image_url",
"image_url": { "url": "asset://asset-20260925080012-x7k2p" },
"role": "reference_image"
}
],
"ratio": "16:9",
"duration": 5,
"resolution": "720p",
"watermark": false,
"generate_audio": false
}'A successful call returns { "id": "cgt-..." }; poll GET .../contents/generations/tasks/{id} for the result.
Assets created through this API do not appear among the account assets in the YingTu web workspace and cannot be selected there. Use them through LaoZhang's Seedance API.
Complete code samples#
The whole flow from verification session to a ready asset. Set the LAOZHANG_API_KEY environment variable first. In production, step 2 is usually triggered by your callback page; the samples poll instead.
import os
import time
import requests
BASE_URL = "https://yingtu.ai/api/seedance-assets"
HEADERS = {"Authorization": f"Bearer {os.environ['LAOZHANG_API_KEY']}"}
def api(method, path, **kwargs):
resp = requests.request(method, BASE_URL + path, headers=HEADERS, timeout=60, **kwargs)
body = resp.json()
if not resp.ok:
err = body["error"]
raise RuntimeError(f"{resp.status_code} {err['code']}: {err['message']} ({err['request_id']})")
return body
# 1. Create a verification session. Your own query parameters on
# callback_url are kept when the person is redirected back.
session = api("POST", "/real-persons/verifications", json={
"callback_url": "https://app.example.com/seedance/verified?user=42",
"language": "en",
})
verification_id = session["verification_id"] # store it in your database next to user 42
print("Send this link to the person:", session["verification_url"])
# 2. Wait for the person to pass. Trigger this when your callback page
# is opened, or just poll. After expires_at this raises 410 verification_expired.
while api("GET", f"/real-persons/verifications/{verification_id}")["status"] != "verified":
time.sleep(5)
# 3. Create the asset from a clear, front-facing photo of the same person
asset = api("POST", "/real-persons", json={
"verification_id": verification_id,
"image_url": "https://cdn.example.com/people/presenter-a.jpg",
"name": "presenter-a",
})
# 4. Wait until the asset is ready
for _ in range(120):
if asset["status"] in ("Active", "Failed"):
break
time.sleep(5)
asset = api("GET", f"/real-persons/{asset['id']}")
print(asset["status"], asset["uri"], asset.get("failure"))// Node.js 18+. Save as real-person.mjs and run: node real-person.mjs
const BASE_URL = "https://yingtu.ai/api/seedance-assets";
const AUTH = { Authorization: "Bearer " + process.env.LAOZHANG_API_KEY };
const JSON_HEADERS = { "Content-Type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function api(path, init = {}) {
const res = await fetch(BASE_URL + path, {
...init,
headers: { ...AUTH, ...init.headers },
signal: AbortSignal.timeout(60_000),
});
const body = await res.json();
if (!res.ok) {
const { code, message, request_id } = body.error;
throw new Error(res.status + " " + code + ": " + message + " (" + request_id + ")");
}
return body;
}
// 1. Create a verification session; your own callback_url parameters come back unchanged
const session = await api("/real-persons/verifications", {
method: "POST",
headers: JSON_HEADERS,
body: JSON.stringify({
callback_url: "https://app.example.com/seedance/verified?user=42",
language: "en",
}),
});
const verificationId = session.verification_id; // store it in your database next to user 42
console.log("Send this link to the person:", session.verification_url);
// 2. Wait for the person to pass; after expires_at this throws 410 verification_expired
while ((await api("/real-persons/verifications/" + verificationId)).status !== "verified") {
await sleep(5000);
}
// 3. Create the asset from a clear, front-facing photo of the same person
let asset = await api("/real-persons", {
method: "POST",
headers: JSON_HEADERS,
body: JSON.stringify({
verification_id: verificationId,
image_url: "https://cdn.example.com/people/presenter-a.jpg",
name: "presenter-a",
}),
});
// 4. Wait until the asset is ready
for (let i = 0; i < 120 && !["Active", "Failed"].includes(asset.status); i++) {
await sleep(5000);
asset = await api("/real-persons/" + asset.id);
}
console.log(asset.status, asset.uri, asset.failure);FAQ#
Does the callback notify my server?
No. It is a 302 redirect of the person's browser; your server only receives a request if their browser actually opens the callback page. Always be able to get the result by polling the GET endpoint.
Why is the verification_id on the callback different from the one I got at creation?
YingTu issues a new encrypted token during the redirect, so the string differs, but it normally refers to the same verification and both work. Match users with your own callback_url parameters instead of comparing the two strings.
What status do I see if the person fails verification?
Still pending, until expires_at passes and the API returns 410. There is no failed state. The person can reopen the same link while it is valid, or you can create a new session.
Can I still use assets after the verification expires?
Yes. Expiry only affects the verification_id; the asset's id and uri never expire.
How many assets can one verification create?
YingTu does not cap it: until expires_at, every POST /real-persons with the same verified verification_id creates a new asset and uses one write. Whether each photo processes successfully is up to Volcengine.
Can I create the link now and send it later?
Better not. verification_id expires 30 minutes after creation, so create the session when the person is ready to verify.
What is the difference between id and uri? Which one do I store?
Store both. id (ast_...) is YingTu's lookup token: it only works with the GET endpoint and only with the key that created it. uri (asset://...) is the asset address at Volcengine, and it is what you pass to video generation.
How long should my database columns be?
ast_... values are usually around 250 characters and ver_... values usually 300 or more, and the length varies. Use an unbounded type such as TEXT, not VARCHAR(255), and do not assume a fixed format.
Why does an unknown ID return 403 instead of 404?
IDs are encrypted tokens and YingTu keeps no list of assets on its side. A token that cannot be decrypted, was altered, belongs to another key, or is sent to the wrong path always returns 403 resource_not_owned.
My POST timed out. Will retrying create a duplicate?
Yes. There is no idempotency key; every successful POST creates a new asset or session and uses one write. Create calls wait for Volcengine synchronously, so set your client timeout to 60 seconds or more. If a retry does create a duplicate, simply leave the extra asset unused.
What happens to existing assets if I rotate my API key?
IDs created with the old key return 403 when queried with the new key. The uri itself is not tied to a key. Before rotating, make sure your assets are Active and that you have stored their uri.
Is there a list or delete endpoint?
Not at the moment. YingTu does not store assets created through the API, so keep id, uri, and your own metadata in your system.
Can I call the API from a browser?
No. The API sends no CORS headers, so browsers block the call, and putting the key in front-end code would leak it. Call it from your server.
Do these endpoints cost anything?
The asset endpoints currently do not charge your LaoZhang balance. Video generation that uses the asset is billed at the normal Seedance price.
Changelog#
- 2026-09-25: Rewrote the documentation after checking every statement against the live API code. Added the end-to-end flow, callback details, the full error table and the FAQ. API behavior did not change.