构建 · 虚拟 Try-On
为你的应用构建虚拟 try-on 功能
一句话总结
在你的后端接收商品图,将其附带 mannequin ID 通过 POST 提交至 /api/v1/tryon/apparel,在后台 worker 中(绝不要在 HTTP 请求主线程中)轮询返回的 generation ID 直至 status 变为 completed,随后将 output_url 持久化保存至你自己的对象存储,并在 UI 中渲染展示。
- Credits: 2K 分辨率 5 credits · 4K 分辨率 6 credits
- 常规耗时: 1.5–4 min
- 处理方式: 异步,轮询
本页所有数据均来自 Photta API 文档 · 已验证
生产级方案实践:顾客上传商品图,你的后端将 Photta try-on 任务加入队列,后台 worker 持续轮询直到完成,前端展示模特上身效果。无阻塞请求,无 API key 泄露风险,无需依赖伪 SDK。
RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
-H "Authorization: Bearer $PHOTTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}),
});
const { data } = await res.json(); // data.id, data.statusimport os, requests
res = requests.post(
"https://api.photta.app/api/v1/tryon/apparel",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": True,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
},
)
data = res.json()["data"] # data["id"], data["status"]工作原理
从上传图片到生成模特上身图
6 个具体步骤。首次接入预计需要 10 到 15 分钟的开发时间。
步骤 1
梳理数据流
顾客设备 → 你的后端(上传)→ Photta API(提交)→ Photta API(轮询)→ 你的数据库(状态同步)→ 顾客设备(渲染)。在写第一行代码前先理清该流程——明确你自身后端与 Photta 之间的边界至关重要。
步骤 2
生成并存储 API key
生产环境密钥以 photta_live_ 开头。请将其存入环境变量中。切勿暴露给浏览器。单个密钥通用 Photta 的所有能力,无需为 try-on、珠宝或假人隐形分别申请单独的密钥。
步骤 3
配置上传 endpoint
首先将顾客上传的商品图存入对象存储。向 Photta 提供公开 URL 比传输 base64 更简洁,同时也为每次请求保留了完整的审计追踪记录。
步骤 4
从服务端提交任务
向 POST /api/v1/tryon/apparel 提交商品 URL、mannequin_id、resolution 和 aspect_ratio。接口将返回 202 Accepted 以及 generation ID。请将该 ID 保存至你的数据库中,以便客户端直接轮询你自己的 endpoint,而非 Photta 的接口。
步骤 5
通过后台 worker 进行轮询
切勿在 HTTP 请求内部进行轮询——任务生成需要 1.5 到 4 分钟,这会超出大部分 serverless 环境的超时限制。推荐使用 Trigger.dev、Inngest、BullMQ、Vercel Cron 或你自己的消息队列,每隔 3 秒重新调度一次轮询任务,直至生成完成。
步骤 6
渲染最终结果
当 status 变为 completed 时,将 data.output_url 保存至你的数据库,并向顾客设备推送实时事件(websocket、SSE 或长轮询)。前端 UI 随即将骨架加载屏替换为生成的图像。流程结束。
三段端到端代码示例
上传处理、任务分发、worker 端轮询器——上线该功能所需的全部组件。
// POST /app/try-on → your backend upload handler
import { put } from "@vercel/blob";
export async function uploadProduct(file: File) {
const { url } = await put(`products/${crypto.randomUUID()}.jpg`, file, {
access: "public",
});
return url;
}const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}),
});
const { data } = await res.json(); // data.id, data.status// Trigger.dev / Inngest / BullMQ task
const BASE = "https://api.photta.app/api/v1";
export const pollTryOnTask = task({
id: "poll-try-on",
run: async ({ generationId, attempt = 0 }) => {
const res = await fetch(`${BASE}/tryon/apparel/${generationId}`, {
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` },
});
const { data } = await res.json();
if (data.status === "completed") return data.output_url;
if (data.status === "failed") throw new Error(data.error_message);
if (attempt >= 120) throw new Error("try-on timed out");
// Re-schedule this same task after 3 seconds.
return ctx.retry.after(3000, { generationId, attempt: attempt + 1 });
},
});开发者常见问题
上线前常见问题
没有后端我可以实现这个吗?
不行。你的 Photta API key 必须保留在服务端。就此而言,即便是一个轻量的 Next.js route handler 或 Cloudflare Worker 也算作后端——该 key 只需要存放在 user-agent 无法读取的地方即可。
我该如何避免 HTTP 请求挂起?
切勿在请求处理程序内部进行 polling。同步提交任务,将 generation ID 返回给客户端,然后在后台 worker 中进行 polling。客户端应当 polling 你的 status endpoint,而不是 Photta 的——这样 rate limit 就由你来掌控,而不是转嫁给每个用户。
如果任务在执行中途失败了会怎样?
失败时 Photta 会自动退还 credits,且响应中会包含 data.error_code + data.error_message。向用户展示友好的提示信息,并提供一个能发起新任务的重试按钮。
我可以展示真实的进度吗?
Photta 不会输出中间进度——状态只有 processing、completed 或 failed。行之有效的做法是根据 1.5–4 分钟的时间窗口校准一个定时进度条,或者使用简单的骨架屏加载器。不要伪造百分比。
结果 URL 是永久有效的吗?
CDN URL 是稳定的,但你的产品不应依赖它来进行长期渲染展示。下载一次字节数据并将其存储在自有的 S3/GCS/R2 bucket 中——这样即使 Photta 的 CDN 发生变动,你的 PDP 也能正常运行。