跳到主要内容

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. 1

    你的服务端

    把角色图片放到公网 HTTPS 地址,调用 POST /api/seedance-assets/virtual-humans。

  2. 2

    YingTu

    校验 Key 和请求体,在火山引擎创建素材,立即返回 202。这时 status 通常是 Processing。

  3. 3

    你的服务端

    保存返回的 id 和 uri,每 5~10 秒调用一次 GET /api/seedance-assets/virtual-humans/{id}。

  4. 4

    火山引擎

    下载并处理图片。完成后状态变成 Active(可用)或 Failed(失败,failure 里有原因)。

  5. 5

    你的服务端

    状态为 Active 后停止轮询,把 uri 作为 reference_image 提交给 LaoZhang 的 Seedance 视频生成接口。之后同一个 uri 可以反复使用。

基础约定#

项目取值说明
Base URLhttps://yingtu.ai下文所有路径都拼在这个域名后面。
认证Authorization: Bearer $LAOZHANG_API_KEYBearer 区分大小写,后面跟一个空格和完整的 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,稍后重试即可。

接口#

创建虚拟人素材

POST/api/seedance-assets/virtual-humans

提交一张图片,创建一个虚拟人素材。接口在火山引擎创建素材后立即返回 202 Accepted,不会等图片处理完。每次调用都会新建一个素材(没有幂等键),并计入每小时 12 次的写入额度。

Shell请求
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_urlstring是角色图片的公网 HTTPS 地址。要求见下方“图片地址要求”。
namestring否素材名称,方便你自己辨认。去掉首尾空格后 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";少数情况下火山引擎会在创建时直接拒绝,返回 502 upstream_error。
HTTP响应 · 202 Accepted
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_idstring本次请求的追踪号,格式为 req_ 加 32 位十六进制。找技术支持时提供它。
idstringYingTu 素材 id,以 ast_ 开头,只用于查询状态。它是加密令牌,通常 250 个字符左右、长度不固定,并绑定创建它的 API Key。原样保存,不要截断或解析。
objectstring固定为 seedance.asset。
kindstring固定为 virtual_human。
namestring素材名称。没有名称时为空字符串。
statusstring素材状态,原样来自火山引擎:Processing、Active 或 Failed。见“状态说明”。
uristring生成视频时使用的素材地址,格式为 asset://asset-...。创建后不会变化。原样使用,不要拼接或改写。
created_atstring可选。火山引擎返回的创建时间;上游没有返回时不出现。
updated_atstring可选。火山引擎返回的最近更新时间。
failureobject可选。只在火山引擎报告错误时出现,形如 { "code": "...", "message": "..." },两个字段都可能是空字符串。

查询虚拟人素材

GET/api/seedance-assets/virtual-humans/{id}

查询素材的最新状态。每次调用都会实时向火山引擎查询,不计入写入额度。只能用创建时的同一个 Key 查询。

Shell请求
# ASSET_ID 是创建接口返回的 id(ast_...)
curl https://yingtu.ai/api/seedance-assets/virtual-humans/$ASSET_ID \
  -H "Authorization: Bearer $LAOZHANG_API_KEY"
路径参数类型必填说明
idstring是创建接口返回的 id(ast_...)。不要传 uri 或 asset-...,那样会返回 403。
HTTP响应 · 200 OK · 可用
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响应 · 200 OK · 处理失败
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错误响应
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
  }
}
HTTPcode什么时候出现怎么处理可重试
400invalid_request请求体不是 JSON 对象、缺少必填字段、字段值不合法,或带了接口没有列出的字段。message 会指出是哪个字段。按 message 修改请求。原样重试不会成功。否
401invalid_api_key没有 Authorization 头、格式不是 Bearer <key>,或 LaoZhang 判定 Key 无效、已停用。检查 Key 和请求头格式。否
403resource_not_ownedid 不是当前 Key 创建的;或被截断、改动;或把真人 id 拿到虚拟人路径查询。不存在和不属于你都返回 403,不会返回 404。确认用的是创建时的同一个 Key、路径类型正确、id 原样未改。否
403capability_unavailable火山引擎侧的素材权益暂不可用。这是 YingTu 服务账号的问题,不是你的 Key。带上 request_id 联系技术支持。否
413payload_too_large请求体超过 16 KB。精简请求体。正常请求远小于这个上限。否
415unsupported_media_typePOST 请求的 Content-Type 不是 application/json。加上 Content-Type: application/json。否
429rate_limit_exceeded当前 Key 超过每小时 12 次写入;或火山引擎侧限流。有 Retry-After 时等待对应秒数;没有时(上游限流)用指数退避重试。是
500internal_errorYingTu 内部未预期的错误。带上 request_id 联系技术支持。POST 可能已部分执行,重试有可能产生重复素材。否
502upstream_error火山引擎返回错误,或返回的数据不完整(例如没有素材 ID、认证链接没通过安全检查)。以 retryable 为准:true 时稍后重试;false 时通常是请求内容被上游拒绝,修改后再试。视情况
503authentication_unavailable暂时无法向 LaoZhang 校验 Key。稍后重试。是
503service_unavailableYingTu 服务端配置暂不可用。稍后重试;持续出现请联系技术支持。是

另外两种不是 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 技术支持。
Text成功的 POST 响应头
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 文档 为准。
ShellcURL · 创建视频任务
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。

PythonPython · requests
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.jsJavaScript · Node.js 18+
// 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:文档按线上接口逐项核对后重写,补充接入流程、回调说明、完整错误码和常见问题。接口行为没有变化。