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
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:
- For asset library APIs, see Asset Library API Overview;
- For video task APIs, see Create Video Task.
https://locopalai.com
Authorization: Bearer <API_KEY>
Content-Type: application/jsonCall 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/generationsCreating 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
- Create a group:
POST /api/v1/materials/groups, settypetovirtual, and savedata.group.idfrom the response. For fields and examples, see Asset Groups. - Submit the image:
POST /api/v1/materials/assetswithgroup_id+image_url(ordata_url), and savedata.asset_id.titlecan be left empty; the platform names it automatically from the file name. - Poll the status:
GET /api/v1/materials/assets/{asset_id}, preferably once every 5–10 seconds, querying only the same asset ID.
data.asset.status | What to do |
|---|---|
Processing | Query the same asset_id later |
Active | You can use asset://asset-id in video requests |
Failed | Read error_message, fix the image, and create a new asset |
2. Portrait Asset QR Code Authorization
POST /api/v1/materials/portrait/sessionsto getauthorization_urlandsession_token; turn the address into a QR code for the person to scan.- After the person completes capture, call
POST /api/v1/materials/portrait/resolve(withsession_token) to get the portrait groupgroup.id. POST /api/v1/materials/syncto sync once, then query the asset list to read the portrait asset'sasset_idandstatus.
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: Readerror_message, fix the source file, and create a new asset.
