Seedance 虚拟人素材 API
用一张公网 HTTPS 图片创建 Seedance 虚拟人素材,拿到 asset:// URI。素材状态变成 Active 后,把 URI 作为 reference_image 传给 Seedance 视频生成,同一个虚拟角色就能在多条视频里保持一致。所有调用都用 LaoZhang API Key,从你的服务端发起。
最后更新:2026-09-25 · 已对照线上接口代码逐项核对
概述#
虚拟人素材适合 AI 生成或虚构的角色形象,比如品牌 IP、数字人、游戏角色。创建一次,之后每条视频都引用同一个 uri,角色的长相就能保持一致。
图片里如果是真实存在的人,请改用 真人素材 API,它要求本人完成授权和活体认证。
整个过程是异步的:创建接口立即返回,火山引擎在后台处理图片,你通过查询接口等到状态变成 Active。
准备工作#
- 一个 LaoZhang API Key。素材接口只校验 Key 是否有效,不限分组;但之后用素材生成视频必须用 SeeDance2 分组的 Key。建议两边用同一个 SeeDance2 Key,因为素材
id只能用创建它的 Key 查询。 - 你自己的服务端。接口不支持浏览器直接调用,Key 也不能放在前端。
- 一个能被公网匿名下载的 HTTPS 图片地址(不需要登录、没有防盗链)。
- 一处持久化存储,用来保存返回的
id和uri。YingTu 不保存通过 API 创建的素材,也没有列表接口。
接入流程#
- 1
你的服务端
把角色图片放到公网 HTTPS 地址,调用
POST /api/seedance-assets/virtual-humans。 - 2
YingTu
校验 Key 和请求体,在火山引擎创建素材,立即返回
202。这时status通常是Processing。 - 3
你的服务端
保存返回的
id和uri,每 5~10 秒调用一次GET /api/seedance-assets/virtual-humans/{id}。 - 4
火山引擎
下载并处理图片。完成后状态变成
Active(可用)或Failed(失败,failure里有原因)。 - 5
你的服务端
状态为
Active后停止轮询,把uri作为reference_image提交给 LaoZhang 的 Seedance 视频生成接口。之后同一个uri可以反复使用。
基础约定#
| 项目 | 取值 | 说明 |
|---|---|---|
| Base URL | https://yingtu.ai | 下文所有路径都拼在这个域名后面。 |
| 认证 | Authorization: Bearer $LAOZHANG_API_KEY | Bearer 区分大小写,后面跟一个空格和完整的 Key。缺失或格式不对返回 401 invalid_api_key。 |
| 请求体 | Content-Type: application/json | 只有 POST 需要。必须是 JSON 对象,不超过 16 KB(16384 字节),不能带接口没有列出的字段。 |
| 响应 | JSON | 成功和失败都带 request_id,同一个值也在 X-Request-Id 响应头里。所有响应都带 Cache-Control: no-store。 |
| 调用位置 | 只能从服务端调用 | 接口不返回 CORS 响应头,浏览器里直接调用会被拦截;而且 Key 不能出现在前端代码里。 |
YingTu 会拿你的 Key 请求 LaoZhang 的 /v1/models 做校验,校验通过的结果在单个服务实例上缓存 5 分钟。所以刚停用的 Key 最多还能继续调用 5 分钟;LaoZhang 校验服务暂时不可用时返回 503 authentication_unavailable,稍后重试即可。
接口#
创建虚拟人素材
提交一张图片,创建一个虚拟人素材。接口在火山引擎创建素材后立即返回 202 Accepted,不会等图片处理完。每次调用都会新建一个素材(没有幂等键),并计入每小时 12 次的写入额度。
curl -X POST https://yingtu.ai/api/seedance-assets/virtual-humans \
-H "Authorization: Bearer $LAOZHANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://cdn.example.com/characters/luna.png",
"name": "luna-v1"
}'| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
image_url | string | 是 | 角色图片的公网 HTTPS 地址。要求见下方“图片地址要求”。 |
name | string | 否 | 素材名称,方便你自己辨认。去掉首尾空格后 1~64 个字符。不需要名称就不要传这个字段;传空字符串或 null 会返回 400。 |
请求体只能包含这两个字段,多出任何字段都会返回 400 Unknown request field。
图片地址要求
- 必须以
https://开头,不能带用户名或密码(https://user:pass@...)。首尾空格会被去掉。 - 主机不能是
localhost、*.localhost、*.local,也不能是内网或保留 IP:0.x、10.x、127.x、169.254.x、172.16.x–172.31.x、192.168.x,以及 IPv6 的::、::1、::ffff:*、fc00::/7、fe80::/10。 - YingTu 只检查地址格式,不会下载图片;真正下载图片的是火山引擎。所以地址必须能被公网匿名访问:不要防盗链,不要需要登录或 Cookie。如果用签名地址(如 OSS、S3、COS 预签名链接),有效期至少要撑到素材变成
Active或Failed。 - 图片本身的问题(内容、格式、尺寸等)通常在异步处理时暴露为
status: "Failed";少数情况下火山引擎会在创建时直接拒绝,返回 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: 11
X-RateLimit-Reset: 1790326800
{
"request_id": "req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a",
"id": "ast_9Qk2vX7mL0pT...cRz4w",
"object": "seedance.asset",
"kind": "virtual_human",
"name": "luna-v1",
"status": "Processing",
"uri": "asset://asset-20260925080012-x7k2p"
}示例里的 id 做了截断。响应字段:
| 字段 | 类型 | 说明 |
|---|---|---|
request_id | string | 本次请求的追踪号,格式为 req_ 加 32 位十六进制。找技术支持时提供它。 |
id | string | YingTu 素材 id,以 ast_ 开头,只用于查询状态。它是加密令牌,通常 250 个字符左右、长度不固定,并绑定创建它的 API Key。原样保存,不要截断或解析。 |
object | string | 固定为 seedance.asset。 |
kind | string | 固定为 virtual_human。 |
name | string | 素材名称。没有名称时为空字符串。 |
status | string | 素材状态,原样来自火山引擎:Processing、Active 或 Failed。见“状态说明”。 |
uri | string | 生成视频时使用的素材地址,格式为 asset://asset-...。创建后不会变化。原样使用,不要拼接或改写。 |
created_at | string | 可选。火山引擎返回的创建时间;上游没有返回时不出现。 |
updated_at | string | 可选。火山引擎返回的最近更新时间。 |
failure | object | 可选。只在火山引擎报告错误时出现,形如 { "code": "...", "message": "..." },两个字段都可能是空字符串。 |
查询虚拟人素材
查询素材的最新状态。每次调用都会实时向火山引擎查询,不计入写入额度。只能用创建时的同一个 Key 查询。
# ASSET_ID 是创建接口返回的 id(ast_...)
curl https://yingtu.ai/api/seedance-assets/virtual-humans/$ASSET_ID \
-H "Authorization: Bearer $LAOZHANG_API_KEY"| 路径参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 创建接口返回的 id(ast_...)。不要传 uri 或 asset-...,那样会返回 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": "virtual_human",
"name": "luna-v1",
"status": "Active",
"uri": "asset://asset-20260925080012-x7k2p",
"created_at": "2026-09-25T08:00:12Z",
"updated_at": "2026-09-25T08:01:03Z"
}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": "virtual_human",
"name": "luna-v1",
"status": "Failed",
"uri": "asset://asset-20260925080012-x7k2p",
"failure": {
"code": "<code returned by Volcengine>",
"message": "<message returned by Volcengine>"
}
}素材处理失败时 HTTP 状态码仍然是 200。请看 status 字段,不要只看状态码。响应字段与创建接口相同。
状态说明#
| status | 含义 | 你该做什么 |
|---|---|---|
Processing | 火山引擎正在下载和处理图片。创建接口返回的通常就是这个状态。 | 每 5~10 秒查询一次。这时还不能用于生成。 |
Active | 处理完成,可以用于视频生成。 | 停止轮询,开始用 uri 生成视频。 |
Failed | 处理失败,failure 里有火山引擎给出的原因。 | 按原因换图片后重新创建,会得到新的 id 和 uri。 |
status 是火山引擎原样返回的值。如果出现表里没有的值,按“未就绪”处理并继续查询;长时间不变时,带上 request_id 联系技术支持。创建时如果 YingTu 没能立即读到状态,也会先返回 Processing。
错误码#
所有错误都用同一种结构返回。请同时看 HTTP 状态码和 error.code;error.retryable 为 true 表示原样重试有可能成功,为 false 表示需要先改请求或联系支持。下面的示例是真实返回:
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 | 什么时候出现 | 怎么处理 | 可重试 |
|---|---|---|---|---|
| 400 | invalid_request | 请求体不是 JSON 对象、缺少必填字段、字段值不合法,或带了接口没有列出的字段。message 会指出是哪个字段。 | 按 message 修改请求。原样重试不会成功。 | 否 |
| 401 | invalid_api_key | 没有 Authorization 头、格式不是 Bearer <key>,或 LaoZhang 判定 Key 无效、已停用。 | 检查 Key 和请求头格式。 | 否 |
| 403 | resource_not_owned | id 不是当前 Key 创建的;或被截断、改动;或把真人 id 拿到虚拟人路径查询。不存在和不属于你都返回 403,不会返回 404。 | 确认用的是创建时的同一个 Key、路径类型正确、id 原样未改。 | 否 |
| 403 | capability_unavailable | 火山引擎侧的素材权益暂不可用。这是 YingTu 服务账号的问题,不是你的 Key。 | 带上 request_id 联系技术支持。 | 否 |
| 413 | payload_too_large | 请求体超过 16 KB。 | 精简请求体。正常请求远小于这个上限。 | 否 |
| 415 | unsupported_media_type | POST 请求的 Content-Type 不是 application/json。 | 加上 Content-Type: application/json。 | 否 |
| 429 | rate_limit_exceeded | 当前 Key 超过每小时 12 次写入;或火山引擎侧限流。 | 有 Retry-After 时等待对应秒数;没有时(上游限流)用指数退避重试。 | 是 |
| 500 | internal_error | YingTu 内部未预期的错误。 | 带上 request_id 联系技术支持。POST 可能已部分执行,重试有可能产生重复素材。 | 否 |
| 502 | upstream_error | 火山引擎返回错误,或返回的数据不完整(例如没有素材 ID、认证链接没通过安全检查)。 | 以 retryable 为准:true 时稍后重试;false 时通常是请求内容被上游拒绝,修改后再试。 | 视情况 |
| 503 | authentication_unavailable | 暂时无法向 LaoZhang 校验 Key。 | 稍后重试。 | 是 |
| 503 | service_unavailable | YingTu 服务端配置暂不可用。 | 稍后重试;持续出现请联系技术支持。 | 是 |
另外两种不是 JSON 错误结构的情况:请求方法不对(例如对创建接口发 GET)时返回 405,没有响应体;真人认证的回调中转地址失效时,本人的浏览器会看到 400,详见“认证回调”。
限流与追踪#
- 每个 API Key 每小时最多 12 次写入请求。写入请求指所有 POST 接口的合计:创建虚拟人素材、创建真人认证会话、创建真人素材。GET 查询不计数。
- 只要 Key 校验通过,POST 就会计数,包括被 400、403、409、410、413、415 拒绝的请求和上游失败的请求。401 不计数。所以请先在本地校验参数再发送。
- 计数窗口从这一小时里的第一次 POST 开始,满 1 小时后整体清零,不是滑动窗口。
- 成功的 POST 响应带
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset(窗口重置时间,Unix 秒)。错误响应不带这三个头。 - 超过限制返回 429
rate_limit_exceeded,并带Retry-After(秒)。火山引擎侧限流同样返回 429,但没有Retry-After。 - 如果这个额度不够你的业务量,请联系 YingTu 技术支持。
X-Request-Id: req_4f0c2a9e8b7d4c1fa3e56b7d9c0e1f2a
X-RateLimit-Limit: 12
X-RateLimit-Remaining: 11
X-RateLimit-Reset: 1790326800在 Seedance 视频生成中使用素材#
视频生成不在 YingTu 的素材接口里完成,而是调用 LaoZhang 的 Seedance 接口 POST https://api.laozhang.ai/seedance/api/v3/contents/generations/tasks。这一步的 Key 必须是 SeeDance2 分组。
- 在
content数组里加一项image_url:url填素材的uri(asset://...),role填reference_image。 - 只能用状态为
Active的素材。还在Processing或已经Failed的素材不要提交。 - 同一个
uri可以在任意多个任务里重复使用,不需要每次重新创建素材。 - 每个任务的参考图数量有上限:Seedance 2.0 系列最多 9 张,Seedance 2.5 最多 30 张。
- 其它参数(模型、比例、时长、分辨率、查询任务结果)以 LaoZhang Seedance 文档 为准。
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": "参考图中的角色在雨夜的霓虹街头慢慢走向镜头,电影感光影" },
{
"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
}'创建成功后返回 { "id": "cgt-..." },再用 GET .../contents/generations/tasks/{id} 查询任务结果。
通过 API 创建的素材不会出现在 YingTu 网页工作台的账号素材里,也不能在工作台里选用;它们只能通过 LaoZhang 的 Seedance API 使用。
完整代码示例#
创建素材并等到可用。运行前先设置环境变量 LAOZHANG_API_KEY。
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
asset = api("POST", "/virtual-humans", json={
"image_url": "https://cdn.example.com/characters/luna.png",
"name": "luna-v1",
})
# 先把 id 和 uri 存进你的数据库,再开始轮询
for _ in range(120): # poll for up to ~10 minutes
if asset["status"] in ("Active", "Failed"):
break
time.sleep(5)
asset = api("GET", f"/virtual-humans/{asset['id']}")
if asset["status"] == "Active":
print("可以用于生成:", asset["uri"])
else:
print("处理失败:", asset["status"], asset.get("failure"))// Node.js 18+,保存为 virtual-human.mjs 后运行:node virtual-human.mjs
const BASE_URL = "https://yingtu.ai/api/seedance-assets";
const AUTH = { Authorization: "Bearer " + process.env.LAOZHANG_API_KEY };
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;
}
let asset = await api("/virtual-humans", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
image_url: "https://cdn.example.com/characters/luna.png",
name: "luna-v1",
}),
});
// 先把 asset.id 和 asset.uri 存进你的数据库,再开始轮询
for (let i = 0; i < 120 && !["Active", "Failed"].includes(asset.status); i++) {
await sleep(5000);
asset = await api("/virtual-humans/" + asset.id);
}
console.log(asset.status, asset.status === "Active" ? asset.uri : asset.failure);常见问题#
素材一直是 Failed,常见原因是什么?
先看 failure.message。常见原因有:图片地址需要登录、有防盗链或签名已过期,火山引擎下载不到;图片内容或格式不符合火山引擎要求;图片里是真实存在的人(应该改用真人素材 API)。
id 和 uri 有什么区别?应该保存哪个?
两个都要保存。id(ast_...)是 YingTu 的查询凭证,只能用来调 GET 查状态,并且只对创建它的 Key 有效。uri(asset://...)是火山引擎里的素材地址,生成视频时用它。
数据库字段应该留多长?
ast_... 通常 250 个字符左右,ver_... 通常 300 个字符以上,而且长度不固定。请用 TEXT 这类不限长度的类型,不要用 VARCHAR(255),也不要假设固定格式。
为什么查询不存在的 id 返回 403,而不是 404?
id 是加密令牌,YingTu 服务端不保存素材列表。令牌解不开、被改过、属于别的 Key,或者路径类型不对,都统一返回 403 resource_not_owned。
POST 超时了,重试会不会重复创建?
会。接口没有幂等键,每次成功的 POST 都会新建一个素材或会话,并消耗一次写入额度。创建接口会同步等待火山引擎返回,客户端超时建议设为 60 秒以上。如果超时后重试,多出来的素材不用即可。
换了 API Key 以后,旧素材怎么办?
旧 Key 创建的 id 用新 Key 查询会返回 403。uri 本身不绑定 Key。建议换 Key 之前先把素材查到 Active,并保存好 uri。
有列表接口或删除接口吗?
目前没有。YingTu 不保存通过 API 创建的素材,请在你自己的系统里记录 id、uri 和业务信息。
能在浏览器前端直接调用吗?
不能。接口不返回 CORS 响应头,浏览器会拦截;而且 Key 放在前端会泄露。请由你的服务端调用。
这些接口收费吗?
素材接口目前不扣 LaoZhang 余额。用素材生成视频时,按 Seedance 视频生成的价格计费。
更新记录#
- 2026-09-25:文档按线上接口逐项核对后重写,补充接入流程、回调说明、完整错误码和常见问题。接口行为没有变化。