# 素材库 API

> 上传并管理参考素材，获取 asset ID，实现角色一致性视频生成

出于防深度伪造要求，Seedance 2.0 **不允许把明星人脸图片直接放进生成请求**。想让视频里的角色保持一致（尤其是真人形象），需要先把图片放进素材库、拿到素材 ID，再在生成请求中以 `asset://素材ID` 引用。

素材库接口采用与火山引擎方舟一致的 **Action 风格**：所有操作都是同一个 URL 上的 POST 请求，通过查询参数 `Action` 区分。

## 鉴权方式

素材管理与真人认证接口支持以下两种鉴权方式。两种方式使用同一份账号素材数据，并受对应 API Key 的额度、状态、有效期和访问策略约束。

### API Key（Bearer）

```
POST https://api.looprouter.com/api/v3/assets?Action=<操作名>&Version=2024-01-01
Authorization: Bearer sk-...
Content-Type: application/json
```

<Tip>
素材库与视频生成可以使用**同一个** LoopRouter API Key，无需单独申请素材库密钥。素材归属于你的账号，其他用户无法查询或引用。
</Tip>

### 火山官方 AK/SK（HMAC-SHA256）

也可以使用 API Key 所绑定的 `Access Key ID（AK）` 和 `Secret Access Key（SK）`，按照火山官方 `HMAC-SHA256` 方式签名。已有火山客户端只需把 Endpoint 换成 `https://api.looprouter.com`，并替换为 LoopRouter API Key 页面生成的 AK/SK；Action、Version、请求体和响应解析均保持不变。

火山签名请求使用根路径 `/`，Service 为 `ark`，Region 推荐 `cn-beijing`。下面示例使用[火山官方 Go SDK](https://github.com/volcengine/volc-sdk-golang)为 `CreateAssetGroup` 请求签名：

```go
package main

import (
  "bytes"
  "io"
  "net/http"
  "os"

  "github.com/volcengine/volc-sdk-golang/base"
)

func main() {
  body := []byte(`{"Name":"我的虚拟主角","GroupType":"AIGC"}`)
  req, err := http.NewRequest(
    http.MethodPost,
    "https://api.looprouter.com/?Action=CreateAssetGroup&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)
}
```

<Note>
AK/SK 方式支持官方 JSON / 公网 URL 素材请求。本地文件 `multipart/form-data` 上传是 LoopRouter 扩展能力，请继续使用 API Key（Bearer）认证。视频生成接口也继续使用 API Key；AK/SK 仅用于本模块的素材管理与真人认证 Action。
</Note>

## 素材类型与处理方式

| 素材类型 | 处理方式 |
| --- | --- |
| 动漫 / 风格化角色、物品、场景 | 可直接用公网 URL 作参考图，无需素材库 |
| AI 生成的虚拟人像 | 上传到普通素材组（`GroupType: AIGC`）即可引用 |
| **真实人脸照片** | 必须先完成 [真人认证](/asset-management/real-person)，上传到认证产生的 `LivenessFace` 素材组 |

## 接口一览

| Action | 用途 |
| --- | --- |
| `CreateAssetGroup` | 创建虚拟形象素材组（GroupType 仅支持 `AIGC`） |
| `ListAssetGroups` | 列出自己的素材组（含真人认证产生的组），支持过滤 / 排序 / 分页 |
| `GetAssetGroup` | 查询单个素材组 |
| `UpdateAssetGroup` | 重命名素材组或修改描述（Description 上限 300 字） |
| `DeleteAssetGroup` | 删除素材组 |
| `CreateAsset` | 注册素材（上传素材）：公网媒体 URL（JSON）或本地文件（multipart）上传，支持图片 / 视频 / 音频，异步处理 |
| `ListAssets` | 列出素材，可按组 / 状态 / 名称过滤，支持排序与分页 |
| `GetAsset` | 查询单个素材（会同步最新处理状态） |
| `UpdateAsset` | 重命名素材 |
| `DeleteAsset` | 删除素材 |
| `CreateVisualValidateSession` | 发起真人认证（见 [真人认证接口](/asset-management/real-person)） |
| `GetVisualValidateResult` | 查询真人认证结果 |

响应统一为方舟风格信封：

```json
{
  "ResponseMetadata": {
    "RequestId": "...",
    "Action": "CreateAsset",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": { "Id": "asset-20260709190143-ntwhb" }
}
```

出错时 `ResponseMetadata` 内会包含 `Error: {Code, Message}`，并伴随对应的 HTTP 状态码。

## 完整流程

<Steps>
  <Step title="创建素材组">

```bash
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=CreateAssetGroup&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Name": "我的虚拟主角", "Description": "短剧项目角色A", "GroupType": "AIGC"}'

# => {"ResponseMetadata": {...}, "Result": {"Id": "group-20260709185838-dcnvd"}}
```

  </Step>
  <Step title="注册素材（上传素材）">

素材注册支持两种上传方式：**公网媒体 URL**（JSON）或**本地媒体文件**（`multipart/form-data`）。以下示例使用 API Key；公网 URL 方式也可以改用上文的火山官方 AK/SK 签名，本地文件方式仅支持 API Key。

素材可以是**图片 / 视频 / 音频**：用 `AssetType` 字段指定为 `Image` / `Video` / `Audio`（大小写不敏感），不传时默认为 `Image`。下方示例以图片为例；注册视频 / 音频时显式带上 `AssetType`（JSON 方式加字段 `"AssetType": "Video"`，本地文件方式加表单字段 `-F "AssetType=Video"`）。上传的文件类型需与 `AssetType` 匹配（`Image` → `image/*`、`Video` → `video/*`、`Audio` → `audio/*`），否则会被拒绝。

**方式一：公网媒体 URL（JSON）**

```bash
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=CreateAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "GroupId": "group-20260709185838-dcnvd",
    "Name": "角色A-正面全身",
    "URL": "https://example.com/character-a.png"
  }'

# => {"ResponseMetadata": {...}, "Result": {"Id": "asset-20260709190143-ntwhb"}}
```

**方式二：本地文件上传（multipart/form-data）**

无需自建公网 URL，直接把本地文件作为 `file` 字段上传即可（`GroupId` / `Name` / `AssetType` 用表单字段传递）：

```bash
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=CreateAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -F "GroupId=group-20260709185838-dcnvd" \
  -F "Name=角色A-正面全身" \
  -F "file=@/path/to/character-a.jpg;type=image/jpeg"

# => {"ResponseMetadata": {...}, "Result": {"Id": "asset-20260709190143-ntwhb"}}
```

**注册视频 / 音频素材**：与上面完全一致，只需带上 `AssetType`（此处以视频为例，音频把值换成 `Audio` 即可）。

```bash
# URL 方式（JSON）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=CreateAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "GroupId": "group-20260709185838-dcnvd",
    "Name": "参考视频-镜头A",
    "AssetType": "Video",
    "URL": "https://example.com/clip-a.mp4"
  }'

# 本地文件方式（multipart/form-data）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=CreateAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -F "GroupId=group-20260709185838-dcnvd" \
  -F "Name=参考视频-镜头A" \
  -F "AssetType=Video" \
  -F "file=@/path/to/clip-a.mp4;type=video/mp4"

# => {"ResponseMetadata": {...}, "Result": {"Id": "asset-20260709190312-vd9kq"}}
```

两种方式返回的信封完全一致，后续轮询状态也相同；**URL 方式**要求公网可访问。素材约束按类型区分：

- **图片（`Image`）**：格式 jpeg/png/webp/bmp/tiff/gif/heic/heif，宽高比 (0.4, 2.5)，边长 (300, 6000)px，单文件小于 30MB。
- **视频（`Video`）/ 音频（`Audio`）**：不同 Seedance 模型支持的输入音视频**时长上限不同**，请以对应模型规格为准；建议上传前记录时长，便于区分哪些素材可用于目标模型。

<Note>
本地文件上传会由平台先把文件暂存到对象存储再注册，因此需要平台已开启对象存储；**URL 方式不受此限制**。真人组（`LivenessFace`）与虚拟组（`AIGC`）都支持这两种注册方式。
</Note>

  </Step>
  <Step title="轮询素材状态至 Active">

```bash
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=GetAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Id": "asset-20260709190143-ntwhb"}'

# => "Result": {"Id": "asset-20260709190143-ntwhb", "Status": "Active", "GroupId": "group-20260709185838-dcnvd", ...}
```

素材注册为异步流程，通常十几秒内完成。建议每 3 秒轮询一次，`Status` 变为 `Active` 后即可使用；`Failed` 需检查图片后重新上传（`Error.Code` / `Error.Message` 字段给出原因）。

  </Step>
  <Step title="在视频生成中引用">

```bash
curl -X POST "https://api.looprouter.com/api/v3/contents/generations/tasks" \
  -H "Authorization: Bearer $LOOPROUTER_API_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
  }'
```

  </Step>
</Steps>

<Warning>
提示词中请用「图片1」「图片2」（按 `content` 数组顺序）指代素材，**不要**把 `asset-` 开头的素材 ID 写进提示词文本。
</Warning>

<Warning>
素材 ID 长期有效并可直接用于生成，请像密钥一样妥善保管，不要泄露给不信任的一方。
</Warning>

## 管理接口

### 列出素材组 / 素材

```bash
# 素材组（可选 GroupType 过滤：AIGC / LivenessFace）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=ListAssetGroups&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Filter": {"GroupType": "AIGC"}}'

# 素材（可按组 / 状态 / 名称过滤）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=ListAssets&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Filter": {"GroupIds": ["group-20260709185838-dcnvd"], "Statuses": ["Active"]}}'
```

**过滤字段 `Filter`**

| 字段 | 适用 | 说明 |
| --- | --- | --- |
| `GroupType` | 素材组 | 素材组类型：`AIGC` / `LivenessFace` |
| `GroupIds` | 素材组 / 素材 | 按素材组 ID 数组过滤（命中任一即返回） |
| `Statuses` | 素材 | 按状态数组过滤：`Processing` / `Active` / `Failed` |
| `Name` | 素材组 / 素材 | 名称模糊匹配（≤64 字符） |

<Note>
为兼容旧写法，`Filter` 也接受单数形式 `GroupId` / `Status`（等价于只含一个元素的对应数组）。
</Note>

**排序**

- `SortBy`：素材支持 `CreateTime` / `UpdateTime` / `GroupId`，素材组支持 `CreateTime` / `UpdateTime`，默认 `CreateTime`。
- `SortOrder`：`Desc`（默认）/ `Asc`。

**分页（两种互斥模式）**

| 模式 | 入参 | 响应 |
| --- | --- | --- |
| 游标分页（默认，推荐） | `MaxResults` 每页条数 1~100（默认 10）、`NextToken` 下一页游标（首次不传） | `Result: {Items, NextToken?}`。**仅当还有下一页时才返回 `NextToken`**；未返回即代表已到末页 |
| 页码分页（兼容） | `PageNumber` ≥1（默认 1）、`PageSize` 1~100（默认 10） | `Result: {Items, TotalCount, PageNumber, PageSize}` |

<Warning>
两种分页模式不能混用：同时传 `PageNumber` / `PageSize` 与 `NextToken` / `MaxResults` 会返回 400 `InvalidParameter`。不传任何分页参数时默认走游标分页、每页 10 条。
</Warning>

游标分页翻页示例：

```bash
# 第一页（每页 20 条）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=ListAssets&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" -H "Content-Type: application/json" \
  -d '{"Filter": {"GroupIds": ["group-20260709185838-dcnvd"]}, "MaxResults": 20}'
# => "Result": {"Items": [...], "NextToken": "<opaque cursor>"}

# 下一页：把上一页返回的 NextToken 原样回传（请勿自行解析或构造）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=ListAssets&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" -H "Content-Type: application/json" \
  -d '{"Filter": {"GroupIds": ["group-20260709185838-dcnvd"]}, "MaxResults": 20, "NextToken": "<opaque cursor>"}'
# => "Result": {"Items": [...]}   # 无 NextToken 即已到末页
```

### 素材 / 素材组字段

素材项字段（`ListAssets` 时为 `Result.Items` 数组的每个元素，`GetAsset` 时直接作为 `Result` 返回）：

| 字段 | 说明 |
| --- | --- |
| `Id` | 素材 ID（`asset-` 开头） |
| `Name` | 素材名称 |
| `URL` | 素材访问链接（**始终返回**；处理中或无图时为空字符串，链接在每次查询时自动刷新） |
| `GroupId` | 所属素材组 ID |
| `AssetType` | 素材类型：`Image` / `Video` / `Audio`（注册时不传默认 `Image`） |
| `Status` | `Processing` / `Active` / `Failed` |
| `Moderation` | 审核策略对象，固定为 `{"Strategy": "Default"}` |
| `Error` | 失败原因对象 `{Code, Message}`，**始终返回**，无错误时两个字段均为空字符串 |
| `ProjectName` | 原样回显请求中的 `ProjectName`（默认 `default`） |
| `CreateTime` / `UpdateTime` | RFC-3339 UTC 时间字符串（如 `2026-07-09T19:01:43Z`） |

素材组项字段（`ListAssetGroups` 时为 `Result.Items` 数组的每个元素，`GetAssetGroup` 时直接作为 `Result` 返回）为：`Id`、`Name`、`Description`、`GroupType`、`ProjectName`、`CreateTime`、`UpdateTime`（**素材组没有 `Status` 字段**）。

<Tip>
`ProjectName` 是可选请求字段，仅用于在响应中原样回显、方便你按项目组织素材；不传时默认为 `default`。
</Tip>

### 重命名 / 修改描述

```bash
# 重命名素材组（可同时改描述，Description 上限 300 字；返回 {"Id": "..."}）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=UpdateAssetGroup&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Id": "group-20260709185838-dcnvd", "Name": "新的组名", "Description": "改后的描述"}'

# 重命名素材（返回 {"Id": "..."}）
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=UpdateAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Id": "asset-20260709190143-ntwhb", "Name": "新的素材名"}'
```

### 删除素材 / 素材组

```bash
curl -X POST "https://api.looprouter.com/api/v3/assets?Action=DeleteAsset&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" -d '{"Id": "asset-20260709190143-ntwhb"}'

curl -X POST "https://api.looprouter.com/api/v3/assets?Action=DeleteAssetGroup&Version=2024-01-01" \
  -H "Authorization: Bearer $LOOPROUTER_API_KEY" \
  -H "Content-Type: application/json" -d '{"Id": "group-20260709185838-dcnvd"}'
```

删除操作成功返回 `"Result": {}`。

## 错误码

| HTTP | Code | 说明 |
| --- | --- | --- |
| 400 | `MissingParameter` / `InvalidParameter` / `InvalidAction` | 缺少 Action、必填字段，或 Action / GroupType / AssetType 不支持 |
| 401 | `Unauthorized` / `InvalidCredential` / `InvalidSignature` | API Key 或 AK/SK 无效，或火山请求签名不匹配 |
| 404 | `NotFound` | 素材/素材组不存在，或不属于当前账号 |
| 429 | `RateLimitExceeded` | 素材操作过于频繁（平台已自动排队重试后仍超限），请稍后重试 |
| 503 | `NoAvailableAssetChannel` | 当前账号的素材库服务暂未就绪，请联系我们开通 |
| 500 | `InternalError` | 素材库服务异常，Message 中包含具体原因 |

<Info>
素材注册操作在平台侧有自动排队机制：短时突发请求会先排队等待（最长 5 秒）而不是立即失败，超出等待窗口才返回 429。
</Info>

## 常见问题

<AccordionGroup>
  <Accordion title="素材可以在控制台网页上管理吗？">
    可以。控制台「资产管理」页面与本 API 操作的是同一个素材库，网页上传的素材同样可以在 API 中引用，反之亦然。
  </Accordion>
  <Accordion title="素材会保存多久？">
    素材 ID 长期有效。平台不长期存储素材原图，图片由官方素材库托管，访问链接会在每次查询时自动刷新。
  </Accordion>
  <Accordion title="不同账号之间的素材隔离吗？">
    是。素材、素材组、认证会话都绑定账号，跨账号查询、引用、删除均会返回 404。
  </Accordion>
  <Accordion title="想让角色一致性更好，怎么选素材？">
    建议同一角色上传两张图并同时引用：一张正面全身照 + 一张表情自然的面部特写。
  </Accordion>
  <Accordion title="真人照片上传报错？">
    真实人脸必须上传到真人认证产生的 `LivenessFace` 素材组。向普通 `AIGC` 组上传真人照片会在内容审核阶段失败（素材状态 `Failed`）。流程见 [真人认证接口](/asset-management/real-person)。
  </Accordion>
</AccordionGroup>

## 相关文档

<CardGroup cols={3}>
  <Card title="真人认证接口" icon="user-check" href="/asset-management/real-person">
    真实人像素材的活体认证流程
  </Card>
  <Card title="素材引用实战" icon="wand-magic-sparkles" href="/asset-management/asset-reference">
    端到端代码示例
  </Card>
  <Card title="视频生成 API" icon="clapperboard" href="/api-capabilities/seedance2/video-generation">
    生成参数详解
  </Card>
</CardGroup>
