Skip to main content
LocoPal AiLocoPal Ai
Asset Library

Guide

Portrait Assets and Reference Video Integration Flow

A complete, runnable flow from creating a group and adding images to the library to submitting multiple images plus a reference video

View Markdown

Portrait Assets and Reference Video Integration Flow

This page gives a server-side call sequence you can put into practice directly. The fields, examples, and error codes for each step are on the API pages; here we only demonstrate the order and the complete code:

https://locopalai.com
Authorization: Bearer <API_KEY>
Content-Type: application/json

Call Order

Virtual Assets

POST /api/v1/materials/groups
  → POST /api/v1/materials/assets
  → GET  /api/v1/materials/assets/{asset_id}   轮询到 status 为 Active
  → POST /v1/video/generations
  → GET  /v1/video/generations/{task_id}

Portrait Assets

POST /api/v1/materials/groups {"type":"real"}
  → POST /api/v1/materials/portrait/sessions   拿到二维码链接
  → 真人本人扫码并完成手机采集
  → POST /api/v1/materials/portrait/resolve
  → POST /api/v1/materials/sync
  → GET  /api/v1/materials/assets?group_id=...
  → POST /v1/video/generations

Creating a real group does not mean the platform completes authorization on the person's behalf; when a real person is involved, that person must still complete the capture and authorization.

1. Virtual Portrait Intake

  1. Create a group: POST /api/v1/materials/groups, set type to virtual, and save data.group.id from the response. For fields and examples, see Asset Groups.
  2. Submit the image: POST /api/v1/materials/assets with group_id + image_url (or data_url), and save data.asset_id. title can be left empty; the platform names it automatically from the file name.
  3. Poll the status: GET /api/v1/materials/assets/{asset_id}, preferably once every 5–10 seconds, querying only the same asset ID.
data.asset.statusWhat to do
ProcessingQuery the same asset_id later
ActiveYou can use asset://asset-id in video requests
FailedRead error_message, fix the image, and create a new asset

2. Portrait Asset QR Code Authorization

  1. POST /api/v1/materials/portrait/sessions to get authorization_url and session_token; turn the address into a QR code for the person to scan.
  2. After the person completes capture, call POST /api/v1/materials/portrait/resolve (with session_token) to get the portrait group group.id.
  3. POST /api/v1/materials/sync to sync once, then query the asset list to read the portrait asset's asset_id and status.

For parameters, responses, and error codes, see Portrait Asset Authorization.

3. Submitting Multiple Images + Reference Video

In the public API, the format of "multiple images + depth video" is: each image occupies its own content element, and the reference video uses video_url with role: "reference_video"; there is no separate deep_video field.

export LOCOPAL_API_KEY="sk-你的APIKey"
export LOCOPAL_VIDEO_MODEL_ID="从 /v1/models 返回的视频模型 ID"
export LOCOPAL_PERSON_IMAGE_URL="https://你的对象存储域名/person.png"
export LOCOPAL_PRODUCT_IMAGE_URL="https://你的对象存储域名/product.png"
export LOCOPAL_REFERENCE_VIDEO_URL="https://你的对象存储域名/motion.mp4"
const origin = 'https://locopalai.com'
const apiKey = process.env.LOCOPAL_API_KEY!
const model = process.env.LOCOPAL_VIDEO_MODEL_ID!
const personImage = process.env.LOCOPAL_PERSON_IMAGE_URL!
const productImage = process.env.LOCOPAL_PRODUCT_IMAGE_URL!
const referenceVideo = process.env.LOCOPAL_REFERENCE_VIDEO_URL!

const response = await fetch(`${origin}/v1/video/generations`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    model,
    content: [
      { type: 'text', text: '以图片1的人物为主体,参考图片2的产品外观,按照参考视频的动作节奏和运镜生成视频。' },
      { type: 'image_url', role: 'reference_image', image_url: { url: personImage } },
      { type: 'image_url', role: 'reference_image', image_url: { url: productImage } },
      { type: 'video_url', role: 'reference_video', video_url: { url: referenceVideo } },
    ],
    resolution: '720p',
    ratio: '16:9',
    duration: 5,
    generate_audio: true,
    watermark: false,
  }),
})

const created = (await response.json()) as { id?: string; error?: { message?: string } }
if (!response.ok || !created.id) throw new Error(created.error?.message ?? `创建视频失败:HTTP ${response.status}`)

// 保存 created.id。网络中断后只查询这个 ID,不要重复创建。
const query = await fetch(`${origin}/v1/video/generations/${encodeURIComponent(created.id)}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
})
if (!query.ok) throw new Error(await query.text())
console.log(await query.json())

If the images have already passed asset library review, simply replace the image URLs with asset://<asset_id>; each image still needs to occupy its own content element. The number of reference images, reference video capability, resolution, duration, and audio capability are subject to the model capabilities returned by /v1/models and to actual error responses.

4. Complete Node.js Example

The code below chains together "create group → submit image → poll status → submit video", reading the API key from environment variables.

const origin = process.env.LOCOPAL_API_ORIGIN ?? 'https://locopalai.com'
const apiKey = process.env.LOCOPAL_API_KEY
const imageUrl = process.env.LOCOPAL_IMAGE_URL
const videoModelId = process.env.LOCOPAL_VIDEO_MODEL_ID

if (!apiKey || !imageUrl || !videoModelId) {
  throw new Error('请设置 LOCOPAL_API_KEY、LOCOPAL_IMAGE_URL、LOCOPAL_VIDEO_MODEL_ID')
}

async function locopal<T>(path: string, init: RequestInit = {}) {
  const response = await fetch(`${origin}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      ...init.headers,
    },
  })
  const payload = (await response.json()) as { code?: number; data?: T; msg?: string }
  if (!response.ok || payload.code !== 0) throw new Error(payload.msg ?? `请求失败:HTTP ${response.status}`)
  return payload.data as T
}

const group = await locopal<{ group: { id: string } }>('/api/v1/materials/groups', {
  method: 'POST',
  body: JSON.stringify({ name: 'API 人像参考素材', description: '虚拟素材分组', type: 'virtual' }),
})

const asset = await locopal<{ asset_id: string }>('/api/v1/materials/assets', {
  method: 'POST',
  body: JSON.stringify({ group_id: group.group.id, title: '人物参考图', image_url: imageUrl }),
})

const state = await locopal<{ asset: { status: string; error_message?: string | null } }>(
  `/api/v1/materials/assets/${encodeURIComponent(asset.asset_id)}`,
)
if (state.asset.status !== 'Active') {
  throw new Error(state.asset.error_message ?? '素材尚未可用')
}

const video = await fetch(`${origin}/v1/video/generations`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    model: videoModelId,
    content: [
      { type: 'text', text: '让人物自然转身,保持人物外观一致。' },
      { type: 'image_url', role: 'reference_image', image_url: { url: `asset://${asset.asset_id}` } },
    ],
    resolution: '720p',
    ratio: '16:9',
    duration: 5,
  }),
})
if (!video.ok) throw new Error(await video.text())
console.log(await video.json())

5. Complete Node.js Portrait Authorization Example

const origin = 'https://locopalai.com'
const apiKey = process.env.LOCOPAL_API_KEY!

async function api<T>(path: string, init: RequestInit = {}) {
  const response = await fetch(`${origin}${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', ...init.headers },
  })
  const payload = (await response.json()) as { code?: number; data?: T; msg?: string }
  if (!response.ok || payload.code !== 0) throw new Error(payload.msg ?? `请求失败:HTTP ${response.status}`)
  return payload.data as T
}

const created = await api<{ session: { authorization_url: string; session_token: string } }>(
  '/api/v1/materials/portrait/sessions',
  { method: 'POST', body: JSON.stringify({ continuous_create: false }) },
)

// 把 authorization_url 转成二维码展示给真人本人。
// 等用户在手机端完成采集后,再执行下面的解析请求。
const resolved = await api<{ group: { id: string; type: string } }>('/api/v1/materials/portrait/resolve', {
  method: 'POST',
  body: JSON.stringify({ session_token: created.session.session_token }),
})

await api('/api/v1/materials/sync', { method: 'POST', body: '{}' })
const assets = await api<{ assets: Array<{ id: string; status: string }> }>(
  `/api/v1/materials/assets?group_id=${encodeURIComponent(resolved.group.id)}&page=1&page_size=20`,
)
console.log({ group: resolved.group, assets })

6. Model Switching and Error Handling

Virtual assets continue to use the same asset://<asset_id>; when switching models, only the model field changes. Portrait assets can only be submitted to models that support the portrait asset library; when the current model does not support them, the API returns a readable error, and you cannot bypass the restriction by changing a portrait asset into an ordinary image URL.

  • 401: Check whether the API key is missing, wrong, or expired;
  • 400: Check whether the request body fields and session token are complete;
  • 404: Check whether the asset ID and group ID belong to the current account;
  • 503: The asset or portrait authorization service is temporarily unavailable; keep the request information and contact platform support;
  • Processing: Continue querying the same asset ID; do not add it to the library again;
  • Failed: Read error_message, fix the source file, and create a new asset.
Was this page helpful?

On this page