LocoPal AiLocoPal Ai

API 文档

素材库 API

AccessKey 签名、虚拟素材入库、单个状态查询与真人扫码授权完整格式

接口路径、认证方式、参数与任务状态说明

素材库 API

素材 API 用于创建虚拟人物分组、提交图片/视频/音频、查询审核状态,以及通过二维码完成真人素材授权。所有 ID 都与 AccessKey 所属用户绑定,跨用户访问会被拒绝。

基础信息

项目
请求方法POST
基础路径https://locopalai.com/api/v3
公共查询参数Action=<接口名>&Version=2024-01-01
请求格式application/json
鉴权AccessKey / SecretKey + HMAC-SHA256
区域与服务cn-beijing / ark

AccessKey

登录平台后打开“AccessKey 管理”,点击创建。创建响应会显示 AccessKey 与 SecretKey,SecretKey 只完整显示一次。

  • AccessKey 用于标识调用方,可在列表中查看掩码。
  • SecretKey 用于签名,无法从列表再次读取。
  • 停用、过期或删除后不能继续调用。
  • 每个 AccessKey 只能访问本账户的分组、素材和真人授权会话。

AccessKey 只用于素材接口,不能代替模型 API Key。两者用途如下:

凭据使用位置请求头/签名用途
API Key/v1/*Authorization: Bearer sk-...查询模型、创建图片/视频任务、查询生成结果。
AccessKey + SecretKey/api/v3HMAC-SHA256创建分组、素材入库、查询审核状态、真人扫码授权。

典型业务会同时使用两套凭据:先用 AccessKey 完成素材入库并等待可用,再用 API Key 提交包含 asset://<素材ID> 的视频请求。

HMAC-SHA256 签名

Content-Type: application/json
Host: locopalai.com
X-Date: 20260908T120000Z
X-Content-Sha256: <实际请求体的 SHA-256 小写十六进制>
Authorization: HMAC-SHA256 Credential=<AccessKey>/<YYYYMMDD>/cn-beijing/ark/request, SignedHeaders=content-type;host;x-content-sha256;x-date, Signature=<signature>

规范化请求:

CanonicalRequest = Method + "\n" + Path + "\n" + CanonicalQuery + "\n" +
  CanonicalHeaders + "\n" + SignedHeaders + "\n" + SHA256(Payload)

Scope = YYYYMMDD/cn-beijing/ark/request
StringToSign = HMAC-SHA256 + "\n" + X-Date + "\n" + Scope + "\n" + SHA256(CanonicalRequest)

CanonicalQuery 按键排序。对 /api/v3?Action=GetAsset&Version=2024-01-01,值为 Action=GetAsset&Version=2024-01-01。签名派生顺序是日期 → 区域 → 服务 → requestStringToSign。请求时间与服务器相差不能超过 15 分钟。

Node.js 完整签名客户端

import crypto from 'node:crypto'

const host = 'locopalai.com'
const accessKey = process.env.LOCOPAL_ACCESS_KEY!
const secretKey = process.env.LOCOPAL_SECRET_KEY!

const sha256 = (value: string | Buffer) =>
  crypto.createHash('sha256').update(value).digest('hex')
const hmac = (key: string | Buffer, value: string) =>
  crypto.createHmac('sha256', key).update(value).digest()

async function materialRequest(action: string, input: Record<string, unknown>) {
  const body = JSON.stringify(input)
  const xDate = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z')
  const shortDate = xDate.slice(0, 8)
  const payloadHash = sha256(body)
  const query = `Action=${encodeURIComponent(action)}&Version=2024-01-01`
  const signedHeaders = 'content-type;host;x-content-sha256;x-date'
  const canonicalHeaders =
    `content-type:application/json\nhost:${host}\n` +
    `x-content-sha256:${payloadHash}\nx-date:${xDate}\n`
  const canonicalRequest = [
    'POST', '/api/v3', query, canonicalHeaders, signedHeaders, payloadHash,
  ].join('\n')
  const scope = `${shortDate}/cn-beijing/ark/request`
  const stringToSign = `HMAC-SHA256\n${xDate}\n${scope}\n${sha256(canonicalRequest)}`
  const signingKey = hmac(hmac(hmac(hmac(secretKey, shortDate), 'cn-beijing'), 'ark'), 'request')
  const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex')

  const response = await fetch(`https://${host}/api/v3?${query}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Date': xDate,
      'X-Content-Sha256': payloadHash,
      Authorization: `HMAC-SHA256 Credential=${accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
    },
    body,
  })
  const payload = await response.json()
  if (!response.ok || payload.ResponseMetadata?.Error) {
    throw new Error(payload.ResponseMetadata?.Error?.Message ?? `HTTP ${response.status}`)
  }
  return payload.Result
}

console.log(await materialRequest('ListAssetGroups', {
  Filter: { GroupType: 'AIGC' },
  PageNumber: 1,
  PageSize: 20,
}))

Python 完整签名客户端

import datetime
import hashlib
import hmac
import json
import os
import urllib.parse
import requests

HOST = "locopalai.com"
ACCESS_KEY = os.environ["LOCOPAL_ACCESS_KEY"]
SECRET_KEY = os.environ["LOCOPAL_SECRET_KEY"]

def sign(key: bytes, value: str) -> bytes:
    return hmac.new(key, value.encode("utf-8"), hashlib.sha256).digest()

def material_request(action: str, payload: dict) -> dict:
    body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
    x_date = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    short_date = x_date[:8]
    payload_hash = hashlib.sha256(body).hexdigest()
    query = urllib.parse.urlencode({"Action": action, "Version": "2024-01-01"})
    signed_headers = "content-type;host;x-content-sha256;x-date"
    canonical_headers = (
        f"content-type:application/json\nhost:{HOST}\n"
        f"x-content-sha256:{payload_hash}\nx-date:{x_date}\n"
    )
    canonical_request = "\n".join([
        "POST", "/api/v3", query, canonical_headers,
        signed_headers, payload_hash,
    ])
    scope = f"{short_date}/cn-beijing/ark/request"
    request_hash = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
    string_to_sign = f"HMAC-SHA256\n{x_date}\n{scope}\n{request_hash}"
    signing_key = sign(sign(sign(sign(SECRET_KEY.encode(), short_date), "cn-beijing"), "ark"), "request")
    signature = hmac.new(signing_key, string_to_sign.encode(), hashlib.sha256).hexdigest()
    authorization = (
        f"HMAC-SHA256 Credential={ACCESS_KEY}/{scope}, "
        f"SignedHeaders={signed_headers}, Signature={signature}"
    )
    response = requests.post(
        f"https://{HOST}/api/v3?{query}",
        data=body,
        headers={
            "Content-Type": "application/json",
            "X-Date": x_date,
            "X-Content-Sha256": payload_hash,
            "Authorization": authorization,
        },
        timeout=30,
    )
    response.raise_for_status()
    result = response.json()
    error = result.get("ResponseMetadata", {}).get("Error")
    if error:
        raise RuntimeError(f"{error.get('Code')}: {error.get('Message')}")
    return result["Result"]

Go 完整签名客户端

func materialRequest(ctx context.Context, action string, input any) (map[string]any, error) {
    host := "locopalai.com"
    accessKey := os.Getenv("LOCOPAL_ACCESS_KEY")
    secretKey := os.Getenv("LOCOPAL_SECRET_KEY")
    body, _ := json.Marshal(input)
    xDate := time.Now().UTC().Format("20060102T150405Z")
    shortDate := xDate[:8]
    shaHex := func(value []byte) string {
        sum := sha256.Sum256(value)
        return hex.EncodeToString(sum[:])
    }
    sign := func(key []byte, value string) []byte {
        mac := hmac.New(sha256.New, key)
        mac.Write([]byte(value))
        return mac.Sum(nil)
    }
    query := "Action=" + url.QueryEscape(action) + "&Version=2024-01-01"
    payloadHash := shaHex(body)
    signedHeaders := "content-type;host;x-content-sha256;x-date"
    canonicalHeaders := "content-type:application/json\nhost:" + host +
        "\nx-content-sha256:" + payloadHash + "\nx-date:" + xDate + "\n"
    canonicalRequest := strings.Join([]string{
        "POST", "/api/v3", query, canonicalHeaders, signedHeaders, payloadHash,
    }, "\n")
    scope := shortDate + "/cn-beijing/ark/request"
    stringToSign := "HMAC-SHA256\n" + xDate + "\n" + scope + "\n" + shaHex([]byte(canonicalRequest))
    key := sign(sign(sign(sign([]byte(secretKey), shortDate), "cn-beijing"), "ark"), "request")
    signature := hex.EncodeToString(sign(key, stringToSign))
    req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
        "https://"+host+"/api/v3?"+query, bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-Date", xDate)
    req.Header.Set("X-Content-Sha256", payloadHash)
    req.Header.Set("Authorization", "HMAC-SHA256 Credential="+accessKey+"/"+scope+
        ", SignedHeaders="+signedHeaders+", Signature="+signature)
    response, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer response.Body.Close()
    var envelope struct {
        Result map[string]any `json:"Result"`
        ResponseMetadata struct {
            Error *struct { Code, Message string } `json:"Error"`
        } `json:"ResponseMetadata"`
    }
    if err := json.NewDecoder(response.Body).Decode(&envelope); err != nil { return nil, err }
    if envelope.ResponseMetadata.Error != nil {
        return nil, fmt.Errorf("%s: %s", envelope.ResponseMetadata.Error.Code, envelope.ResponseMetadata.Error.Message)
    }
    return envelope.Result, nil
}

签名必须基于最终发送的原始字节计算。不要先对一个 JSON 字符串签名,再让 HTTP SDK 重新格式化请求体。

通用响应体

成功:

{
  "ResponseMetadata": {
    "RequestId": "request-id",
    "Action": "CreateAssetGroup",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {}
}

失败:

{
  "ResponseMetadata": {
    "RequestId": "request-id",
    "Action": "GetAsset",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing",
    "Error": {
      "Code": "AccessDenied",
      "Message": "素材不存在或无权访问"
    }
  }
}

业务校验错误可能仍返回 HTTP 200,因此必须同时检查 ResponseMetadata.Error

CreateAssetGroup - 创建虚拟分组

POST /api/v3?Action=CreateAssetGroup&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json
成功判定: HTTP 成功且 ResponseMetadata.Error 不存在

该接口只创建虚拟人物分组。真人分组必须使用后文的二维码授权接口。

参数类型必填说明
Namestring分组名称,1 至 64 个字符。
Descriptionstring分组说明,最多 300 个字符。
GroupTypestring仅使用 AIGC;省略时默认 AIGC
{
  "Name": "品牌虚拟角色",
  "Description": "产品宣传片角色",
  "GroupType": "AIGC"
}

响应字段

字段类型说明
ResponseMetadata.RequestIdstring本次请求标识,排查问题时保留。
Result.Idstring平台分组 ID,后续分组和素材接口都使用它。

分组创建后只需保存一个 Result.Id,不要为不同模型重复创建同名分组。

{
  "ResponseMetadata": {
    "RequestId": "request-create-group",
    "Action": "CreateAssetGroup",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": { "Id": "group-virtual-id" }
}

ListAssetGroups - 查询分组列表

POST /api/v3?Action=ListAssetGroups&Version=2024-01-01

参数类型必填说明
Filter.GroupIdsstring[]只查询指定分组。
Filter.GroupTypestringAIGCLivenessFace
Filter.Namestring名称筛选。
PageNumberinteger页码,默认 1。
PageSizeinteger每页数量,默认 20,最大 100。
SortBystringCreateTimeUpdateTime
SortOrderstringDescAsc
{
  "Filter": { "GroupType": "AIGC", "Name": "品牌" },
  "PageNumber": 1,
  "PageSize": 20,
  "SortBy": "CreateTime",
  "SortOrder": "Desc"
}
{
  "ResponseMetadata": {
    "RequestId": "request-list-groups",
    "Action": "ListAssetGroups",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {
    "Items": [
      {
        "Id": "group-virtual-id",
        "Name": "品牌虚拟角色",
        "Description": "产品宣传片角色",
        "GroupType": "AIGC",
        "CreateTime": "2026-09-08T12:00:00Z",
        "UpdateTime": "2026-09-08T12:00:00Z"
      }
    ],
    "TotalCount": 1,
    "PageNumber": 1,
    "PageSize": 20
  }
}

GetAssetGroup - 查询单个分组

POST /api/v3?Action=GetAssetGroup&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

参数类型必填说明
IdstringCreateAssetGroup 或真人授权解析返回的分组 ID。
{ "Id": "group-virtual-id" }
{
  "ResponseMetadata": {
    "RequestId": "request-get-group",
    "Action": "GetAssetGroup",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {
    "Id": "group-virtual-id",
    "Name": "品牌虚拟角色",
    "Description": "产品宣传片角色",
    "GroupType": "AIGC"
  }
}

UpdateAssetGroup - 更新分组

POST /api/v3?Action=UpdateAssetGroup&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

参数类型必填说明
Idstring本账户拥有的分组 ID。
Namestring新名称;省略时保留原名称。
Descriptionstring新说明;传空字符串可清空说明。
{
  "Id": "group-virtual-id",
  "Name": "品牌角色库",
  "Description": "更新后的说明"
}
{
  "ResponseMetadata": {
    "RequestId": "request-update-group",
    "Action": "UpdateAssetGroup",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": { "Id": "group-virtual-id" }
}

DeleteAssetGroup - 删除分组

POST /api/v3?Action=DeleteAssetGroup&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

{ "Id": "group-virtual-id" }
{
  "ResponseMetadata": {
    "RequestId": "request-delete-group",
    "Action": "DeleteAssetGroup",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {}
}

删除分组会使组内素材不再可用。业务系统应在删除前确认没有运行中的任务仍引用这些素材。

CreateAsset - 往分组插入素材

POST /api/v3?Action=CreateAsset&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json
成功判定: 返回素材 ID 表示已受理,最终可用性以 GetAsset 为准

参数类型必填说明
GroupIdstring当前账户拥有的虚拟素材分组 ID。
AssetTypestringImageVideoAudio
Namestring素材显示名称。
URLstring可被服务端直接下载的公网 HTTPS 地址。
{
  "GroupId": "group-virtual-id",
  "AssetType": "Image",
  "Name": "角色正面图",
  "URL": "https://cdn.example.com/character-front.png"
}
{
  "ResponseMetadata": {
    "RequestId": "request-create-asset",
    "Action": "CreateAsset",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": { "Id": "asset-id" }
}

Result.Id 必须持久化。创建成功只表示已经接收,不表示审核和预处理完成。

调用方切换模型时仍提交同一个 asset://<Result.Id>,不需要查询或替换其他 ID。

URL 必须是公网 HTTPS,不能依赖 Cookie、Referer 或临时浏览器会话;Content-Type 应与真实文件一致。图片使用 JPEG、PNG、WebP 等位图格式,不要提交 SVG 页面。链接有效期应覆盖下载和处理时间。

GetAsset - 查询单个素材状态

POST /api/v3?Action=GetAsset&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

参数类型必填说明
IdstringCreateAsset 返回的素材 ID。
{ "Id": "asset-id" }

处理中:

{
  "ResponseMetadata": {
    "RequestId": "request-get-asset",
    "Action": "GetAsset",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {
    "Id": "asset-id",
    "GroupId": "group-virtual-id",
    "Name": "角色正面图",
    "URL": "https://cdn.example.com/character-front.png",
    "AssetType": "Image",
    "Status": "Processing",
    "Error": { "Code": "", "Message": "" },
    "ModelReadiness": [
      {
        "Models": ["doubao-seedance-2.0-B", "doubao-seedance-2.0-B-fast"],
        "Status": "Active"
      },
      {
        "Models": ["doubao-seedance-2.0"],
        "Status": "Processing"
      }
    ]
  }
}

可用:

{
  "Result": {
    "Id": "asset-id",
    "GroupId": "group-virtual-id",
    "AssetType": "Image",
    "Status": "Active",
    "Error": { "Code": "", "Message": "" }
  }
}

失败:

{
  "Result": {
    "Id": "asset-id",
    "Status": "Failed",
    "Error": {
      "Code": "ResourceDownloadFailed",
      "Message": "resource download failed"
    }
  }
}

程序应在 Processing 时继续查询同一素材 ID,在 Active 后允许进入生成,在 Failed 时向用户展示 Error.Message 并停止自动重试。

ModelReadiness 字段

字段类型说明
Modelsstring[]当前准备状态对应的模型 ID。
StatusstringPreparingProcessingActiveFailedUnavailable
Error.Messagestring当前模型组不可用时的具体原因。

若请求即将使用模型 doubao-seedance-2.0,应找到 Models 包含该 ID 的项目,并等待其 StatusActive。没有读取 ModelReadiness 的客户端也可以直接提交视频;平台会在提交前再次校验,并在尚未可用时返回可重试错误,不会把错误的素材 ID交给视频模型。

async function checkModelAsset(assetId: string, model: string) {
  const asset = await materialRequest('GetAsset', { Id: assetId })
  const readiness = asset.ModelReadiness?.find((item: any) =>
    item.Models?.includes(model),
  )
  const status = readiness?.Status ?? asset.Status
  if (status === 'Active') return { ready: true, asset }
  if (['Failed', 'Unavailable'].includes(status)) {
    throw new Error(readiness?.Error?.Message ?? asset.Error?.Message ?? '素材不可用')
  }
  return { ready: false, status }
}

未就绪时由业务队列按自身调度策略再次调用,不要重复执行 CreateAsset。页面刷新、进程重启或网络恢复后仍查询同一个素材 ID。

ListAssets - 查询素材列表

POST /api/v3?Action=ListAssets&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

参数类型必填说明
Filter.GroupIdsstring[]分组 ID 列表。
Filter.GroupTypestringAIGCLivenessFace
Filter.Statusesstring[]ProcessingActiveFailed
Filter.Namestring名称筛选。
PageNumberinteger默认 1。
PageSizeinteger默认 20,最大 100。
SortBystringCreateTimeUpdateTimeGroupId
SortOrderstringDescAsc
{
  "Filter": {
    "GroupIds": ["group-virtual-id"],
    "Statuses": ["Active"]
  },
  "PageNumber": 1,
  "PageSize": 20,
  "SortBy": "CreateTime",
  "SortOrder": "Desc"
}
{
  "ResponseMetadata": {
    "RequestId": "request-list-assets",
    "Action": "ListAssets",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {
    "Items": [
      {
        "Id": "asset-id",
        "GroupId": "group-virtual-id",
        "Name": "角色正面图",
        "URL": "https://cdn.example.com/character-front.png",
        "AssetType": "Image",
        "Status": "Active",
        "Error": { "Code": "", "Message": "" }
      }
    ],
    "TotalCount": 1,
    "PageNumber": 1,
    "PageSize": 20
  }
}

UpdateAsset 与 DeleteAsset

两个接口都使用 AccessKey HMAC-SHA256 和 application/json 请求体。修改名称和删除都只需要使用平台返回的公开素材 ID。

更新素材名称:

{ "Id": "asset-id", "Name": "角色正面定妆图" }
{
  "ResponseMetadata": {
    "RequestId": "request-update-asset",
    "Action": "UpdateAsset",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": { "Id": "asset-id" }
}

删除素材:

{ "Id": "asset-id" }
{
  "ResponseMetadata": {
    "RequestId": "request-delete-asset",
    "Action": "DeleteAsset",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {}
}

真人素材授权与同步

真人素材分组不能通过 CreateAssetGroup 创建,也不需要在网页或 API 中提前填写名称和说明。用户在手机采集流程中填写,解析成功后返回。

1. 用户同意协议

调用方必须先向用户展示真人素材授权与个人信息处理规则,并记录用户主动同意。不要在用户未同意时生成采集二维码。

2. CreatePortraitSession

POST /api/v3?Action=CreatePortraitSession&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

参数类型必填说明
ContinuousCreateboolean是否允许在同一采集流程中连续创建,默认 false
{ "ContinuousCreate": false }
{
  "ResponseMetadata": {
    "RequestId": "request-create-portrait-session",
    "Action": "CreatePortraitSession",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {
    "H5Link": "https://example.com/portrait-session",
    "BytedToken": "portrait-session-token",
    "ExpiresIn": 600,
    "ExpireTime": "2026-09-08T12:10:00Z"
  }
}

调用方应将 H5Link 生成为二维码,同时把 BytedToken 只保存在创建会话的用户服务端状态中。二维码过期后重新创建,不要把 Token 转给其他账户。

3. 用户手机采集

用户本人扫码,在手机端完成授权、活体采集、分组名称和说明。该步骤不能由服务端接口或普通图片上传代替。

4. ResolveBytedToken

POST /api/v3?Action=ResolveBytedToken&Version=2024-01-01

认证方式: AccessKey HMAC-SHA256
请求体: application/json

{ "BytedToken": "portrait-session-token" }

采集尚未完成:

{
  "ResponseMetadata": {
    "RequestId": "request-resolve-pending",
    "Action": "ResolveBytedToken",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing",
    "Error": {
      "Code": "ValidationError",
      "Message": "真人授权尚未完成,请完成扫码后重试"
    }
  }
}

采集完成:

{
  "ResponseMetadata": {
    "RequestId": "request-resolve-success",
    "Action": "ResolveBytedToken",
    "Version": "2024-01-01",
    "Service": "ark",
    "Region": "cn-beijing"
  },
  "Result": {
    "GroupId": "group-liveness-id",
    "GroupName": "演员 A",
    "GroupDescription": "手机端填写的说明"
  }
}

解析成功后会话即失效。保存 GroupId,然后用 ListAssets 查询真人素材:

{
  "Filter": {
    "GroupIds": ["group-liveness-id"],
    "GroupType": "LivenessFace"
  },
  "PageNumber": 1,
  "PageSize": 20
}

素材由手机采集流程产生,不要再用普通上传重复插入真人素材。每个素材仍需检查 Status;只有 Active 才能用于支持真人素材的模型。

在视频中引用素材

{
  "type": "image_url",
  "role": "reference_image",
  "image_url": { "url": "asset://asset-id" }
}

直接上传的临时参考素材使用公网 HTTPS URL 或接口明确支持的内联数据,不需要先进入素材库。素材库引用与本次临时引用是两种不同方式。

在不同视频模型中使用素材

视频请求使用 /v1/models 当前返回的模型 ID。更换模型时只替换请求中的 model,素材仍使用 CreateAsset 返回的同一个 ID:

{
  "model": "doubao-seedance-2.0",
  "content": [
    { "type": "text", "text": "保持角色外观一致,完成自然转身" },
    {
      "type": "image_url",
      "role": "reference_image",
      "image_url": { "url": "asset://asset-id" }
    }
  ],
  "resolution": "720p",
  "ratio": "16:9",
  "duration": 5
}

asset://asset-id 保持不变。提交前通过 GetAsset 确认素材为 Active,并在模型详情中确认所选模型支持当前参考素材类型。

从入库到视频的完整调用顺序

  1. 使用 AccessKey 调用 CreateAssetGroup,保存 Result.Id
  2. 使用同一 AccessKey 调用 CreateAsset,保存 Result.Id
  3. 使用 GetAsset 查询素材状态。
  4. 素材状态为 Active 后,使用 API Key 调用 /v1/video/generations
  5. content 中提交 asset://<素材ID>;多图时每张图各占一个条目。
  6. 保存视频任务 ID,调用 /v1/video/generations/{task_id} 查询结果。
  7. 切换模型时只改变 model,继续使用同一个平台素材 ID。

状态与客户端行为

状态含义客户端行为
Processing下载、解析或审核中保留原素材 ID,稍后查询。
Active可用于模型请求允许选择并提交 asset://
Failed处理失败展示 Error.CodeError.Message,修正后重新创建素材。

不要用固定等待秒数代替状态查询,也不要在 Processing 时重复创建相同素材。

安全与用户隔离

  • AccessKey 只能访问所属账户的分组和素材。
  • 真人会话 Token 只能由创建该会话的账户解析。
  • 同一真人分组不能绑定多个平台账户。
  • 不要在日志、工单或前端代码中记录 SecretKey、完整 Token 或身份证明信息。
  • 面向终端用户时展示可理解的失败原因,同时保留 RequestId 供技术排查。

本页目录