链路总览
1
创建素材组
Action=CreateAssetGroup(虚拟形象)或走真人认证获得素材组2
注册素材
Action=CreateAsset,提交公网媒体 URL(图片 / 视频 / 音频,用 AssetType 指定,默认图片)3
等待素材 Active
Action=GetAsset 每 3 秒轮询,直到 Status: "Active"4
发起生成
POST /api/v3/contents/generations/tasks,image_url 填 asset://素材ID5
轮询任务
每 15–30 秒查询,直到
succeeded6
下载成片
从
content.video_url 下载(2 小时内有效,不带 Authorization 头)提示词中用「图片1」「图片2」按
content 顺序指代素材,不要出现 asset- 开头的素材 ID 字面量。鉴权方式
本页的素材管理 Action 支持两种认证方式:- API Key:
Authorization: Bearer sk-...,下方完整示例默认使用这种方式。 - 火山官方 AK/SK:使用 API Key 所绑定的 AK/SK,按火山官方
HMAC-SHA256方式签名;请求根路径为/,Service 为ark,Region 推荐cn-beijing。
package main
import (
"bytes"
"io"
"net/http"
"os"
"github.com/volcengine/volc-sdk-golang/base"
)
func main() {
body := []byte(`{"GroupId":"group-xxx","Name":"角色A-全身","URL":"https://example.com/character-a.png"}`)
req, err := http.NewRequest(
http.MethodPost,
"https://api.looprouter.com/?Action=CreateAsset&Version=2024-01-01",
bytes.NewReader(body),
)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
credentials := base.Credentials{
AccessKeyID: os.Getenv("LOOPROUTER_AK"),
SecretAccessKey: os.Getenv("LOOPROUTER_SK"),
Region: "cn-beijing",
Service: "ark",
}
req = credentials.Sign(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
}
AK/SK 仅替代素材管理与真人认证 Action 的 Bearer 认证。
POST /api/v3/contents/generations/tasks 等视频生成接口仍使用 API Key;本地文件 multipart/form-data 上传也继续使用 API Key。完整代码
import time
import requests
BASE = "https://api.looprouter.com"
HEADERS = {"Authorization": "Bearer sk-你的APIKey", "Accept-Encoding": "identity"}
def asset_action(name, body):
resp = requests.post(
f"{BASE}/api/v3/assets?Action={name}&Version=2024-01-01",
headers=HEADERS, json=body,
)
resp.raise_for_status()
return resp.json()["Result"]
def create_asset(group_id, name, url):
result = asset_action("CreateAsset", {"GroupId": group_id, "Name": name, "URL": url})
asset_id = result["Id"]
deadline = time.time() + 90
while time.time() < deadline:
result = asset_action("GetAsset", {"Id": asset_id})
if result["Status"] == "Active":
return asset_id
if result["Status"] == "Failed":
raise RuntimeError(f"素材处理失败: {result.get('Error', {}).get('Message')}")
time.sleep(3)
raise TimeoutError("素材处理超时,请稍后用 GetAsset 复查")
def generate_video(content):
resp = requests.post(
f"{BASE}/api/v3/contents/generations/tasks",
headers=HEADERS,
json={
"model": "doubao-seedance-2-0-260128",
"content": content,
"resolution": "720p",
"ratio": "adaptive",
"duration": 5,
},
)
resp.raise_for_status()
task_id = resp.json()["id"]
while True:
task = requests.get(
f"{BASE}/api/v3/contents/generations/tasks/{task_id}", headers=HEADERS
).json()
if task["status"] in ("succeeded", "failed", "cancelled", "expired"):
return task
time.sleep(20)
if __name__ == "__main__":
group = asset_action("CreateAssetGroup", {"Name": "短剧角色A", "GroupType": "AIGC"})
asset_id = create_asset(group["Id"], "角色A-全身", "https://example.com/character-a.png")
task = generate_video([
{"type": "text", "text": "图片1中的角色撑着伞走在雨中的巷子里,镜头跟随"},
{"type": "image_url", "image_url": {"url": f"asset://{asset_id}"}, "role": "reference_image"},
])
if task["status"] != "succeeded":
raise SystemExit(f"生成失败: {task.get('error')}")
video_url = task["content"]["video_url"]
with requests.get(video_url, stream=True) as r: # 不带 Authorization 头
with open("output.mp4", "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
print("已保存 output.mp4")
KEY="sk-你的APIKey"
BASE="https://api.looprouter.com"
# 1. 创建素材组
curl -X POST "$BASE/api/v3/assets?Action=CreateAssetGroup&Version=2024-01-01" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"Name": "短剧角色A", "GroupType": "AIGC"}'
# => Result.Id = group-20260709185838-dcnvd
# 2. 注册素材
curl -X POST "$BASE/api/v3/assets?Action=CreateAsset&Version=2024-01-01" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"GroupId": "group-20260709185838-dcnvd", "Name": "角色A-全身", "URL": "https://example.com/character-a.png"}'
# => Result.Id = asset-20260709190143-ntwhb
# 3. 轮询素材状态到 Active
curl -X POST "$BASE/api/v3/assets?Action=GetAsset&Version=2024-01-01" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"Id": "asset-20260709190143-ntwhb"}'
# 4. 引用素材生成视频
curl -X POST "$BASE/api/v3/contents/generations/tasks" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"model": "doubao-seedance-2-0-260128",
"content": [
{"type": "text", "text": "图片1中的角色撑着伞走在雨中的巷子里,镜头跟随"},
{"type": "image_url", "image_url": {"url": "asset://asset-20260709190143-ntwhb"}, "role": "reference_image"}
],
"resolution": "720p",
"duration": 5
}'
# => {"id": "task_xxxx"}
# 5. 轮询任务
curl "$BASE/api/v3/contents/generations/tasks/task_xxxx" -H "Authorization: Bearer $KEY"
# 6. 下载成片(不带 Authorization 头)
curl -o output.mp4 "<content.video_url>"
const BASE = "https://api.looprouter.com";
const KEY = process.env.LOOPROUTER_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function assetAction(name, body) {
const res = await fetch(`${BASE}/api/v3/assets?Action=${name}&Version=2024-01-01`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${KEY}` },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${name} failed: ${await res.text()}`);
return (await res.json()).Result;
}
async function main() {
const group = await assetAction("CreateAssetGroup", { Name: "短剧角色A", GroupType: "AIGC" });
const { Id: assetId } = await assetAction("CreateAsset", {
GroupId: group.Id,
Name: "角色A-全身",
URL: "https://example.com/character-a.png",
});
for (;;) {
const asset = await assetAction("GetAsset", { Id: assetId });
if (asset.Status === "Active") break;
if (asset.Status === "Failed") throw new Error(`素材处理失败: ${asset.Error?.Message}`);
await sleep(3000);
}
const createRes = await fetch(`${BASE}/api/v3/contents/generations/tasks`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${KEY}` },
body: JSON.stringify({
model: "doubao-seedance-2-0-260128",
content: [
{ type: "text", text: "图片1中的角色撑着伞走在雨中的巷子里,镜头跟随" },
{ type: "image_url", image_url: { url: `asset://${assetId}` }, role: "reference_image" },
],
resolution: "720p",
duration: 5,
}),
});
const { id } = await createRes.json();
for (;;) {
await sleep(20000);
const task = await (
await fetch(`${BASE}/api/v3/contents/generations/tasks/${id}`, {
headers: { Authorization: `Bearer ${KEY}` },
})
).json();
if (task.status === "succeeded") return console.log("视频地址:", task.content.video_url);
if (["failed", "cancelled", "expired"].includes(task.status))
throw new Error(JSON.stringify(task.error));
}
}
main();
多素材进阶用法
- 一次请求最多引用 9 张参考图,
asset://、公网 URL、Base64 可以混用;但真实人脸只能走asset://(且需真人认证)。 - 可再叠加 0–3 段参考视频 与 0–3 段参考音频(音频不能单独出现,须至少配一张图或一段视频);携带参考视频的请求自动享受视频输入计费折扣。
- 同一角色建议同时引用「全身照 + 面部特写」两张素材,提示词写「图片1为角色全身,图片2为角色面部」。
- 同一请求中的多个
asset://素材必须来自同一账号。
常见报错速查
| 现象 | 原因 | 处理 |
|---|---|---|
400 asset not found or not active | 素材还在 Processing 或 ID 写错 | 先 GetAsset 确认 Active |
| 400 素材引用相关 | 同一请求混用了不同账号的素材 | 同一请求只引用同一账号的素材,必要时重建素材组 |
| 403 内容审核 | 真实人脸未走认证直传 | 完成 真人认证 后用 asset:// 引用 |
素材 Failed | 素材格式/尺寸/内容不合规,或与 AssetType 不匹配 | 按 Error.Message 修正后重新 CreateAsset |
| 视频 404 下载失败 | video_url 已过期(约 2 小时) | 重新查询任务获取新链接,尽快转存 |
| Python JSON 解析异常 | 网关 gzip 协商问题 | 请求头加 Accept-Encoding: identity |
相关页面
能力总览
模型规格与定价
视频生成 API
全部生成参数
素材库 API
素材管理接口