Build · Virtual Try-On
Build a virtual try-on feature for your app
In one sentence
Accept the product photo in your backend, POST it to /api/v1/tryon/apparel with a mannequin ID, poll the returned generation ID from a background worker (never the HTTP request thread) until status flips to completed, then persist output_url into your own object storage and render it in the UI.
- Credits: 5 credits at 2K · 6 credits at 4K
- Typical time: 1.5–4 min
- Processing: async, polling
Every figure on this page comes from the Photta API docs · checked
A production-shaped recipe: customer uploads a product photo, your backend queues a Photta try-on job, a worker polls until it lands, the frontend shows the on-model result. No blocking requests, no leaked API keys, no fake 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"]How it works
From upload to on-model image
Six concrete steps. Expect ten to fifteen minutes of integration work the first time.
Step 1
Sketch the data flow
Customer device → your backend (upload) → Photta API (submit) → Photta API (poll) → your database (status mirror) → customer device (render). Draw this on paper before you write the first line — the boundary between your backend and Photta's is the only thing that matters.
Step 2
Generate and store an API key
Live keys start with photta_live_. Put it in an environment variable. Never ship it to the browser. One key covers every Photta capability, so you don't need separate keys for try-on, jewelry or ghost mannequin.
Step 3
Wire up the upload endpoint
Accept the customer's product photo into object storage first. Giving Photta a public URL is simpler than base64 and gives you an audit trail for every request.
Step 4
Submit the job from the server
POST /api/v1/tryon/apparel with the product URL, mannequin_id, resolution and aspect_ratio. The API returns 202 Accepted + a generation ID. Store the ID in your database so your client can poll your own endpoint, not Photta's.
Step 5
Poll from a background worker
Do not poll inside the HTTP request — the job takes 1.5 to 4 minutes, which exceeds most serverless timeouts. Use Trigger.dev, Inngest, BullMQ, Vercel Cron or your own queue to re-schedule a poll every 3 seconds until the job completes.
Step 6
Render the result
When status flips to completed, copy data.output_url into your database and emit a realtime event (websocket, SSE or long-poll) to the customer's device. The UI swaps the loading skeleton for the image. Done.
Three code samples, end to end
Upload handler, job dispatcher, worker-side poller — every piece you need to ship the feature.
// 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 });
},
});Questions developers ask
Questions before you ship
Can I do this without a backend?
No. Your Photta API key must stay server-side. Even a thin Next.js route handler or a Cloudflare Worker counts as a backend for this purpose — the key just needs to live somewhere a user-agent can't read it.
How do I avoid hanging HTTP requests?
Never poll inside a request handler. Submit the job synchronously, return the generation ID to the client, and poll from a background worker. The client polls YOUR status endpoint, not Photta's — that way rate limits are your problem to manage, not each user's.
What happens if a job fails mid-flight?
Photta refunds the credits automatically on failure, and the response carries data.error_code + data.error_message. Surface a friendly message to the user and offer a retry button that kicks off a fresh job.
Can I show real progress?
Photta doesn't emit intermediate progress — status is either processing, completed or failed. What works well is a timed progress bar calibrated to the 1.5–4 minute window, or a simple skeleton loader. Don't lie about percentages.
Is the result URL permanent?
The CDN URL is stable, but your product shouldn't depend on it for long-term rendering. Download the bytes once and store them in your own S3/GCS/R2 bucket — then your PDP keeps working even if Photta's CDN moves.
Related pages
Reference
Capabilities
Virtual try-on API for apparel
REST API that places apparel on AI mannequins. Asynchronous, 1.5–4 minute generation, 5 credits at 2K, 6 at 4K. cURL, Node.js and Python examples.
ReadQuickstart
First Photta image in under five minutes.
Sign up, generate an API key, run your first cURL call, poll until it completes, persist the result. Five steps, documented end-to-end.
ReadLanguages
Node.js walkthrough
ReadLanguages
Python walkthrough
Read
Every figure on this page comes from the Photta API docs · checked
Ship virtual try-on in an afternoon
Every code sample on this page matches the official Photta docs. Grab an API key and you'll have a working prototype before the coffee finishes brewing.