Building MIRROR: A Luxury AI Fashion Try-On App with Perfect Corp APIs
Source: Dev.to
What Is MIRROR?
MIRROR is a full‑stack web app that combines a high‑end product‑browsing experience with AI‑powered virtual try‑on.
- A user browses products.
- Opens a try‑on modal, uploads a photo, and sees an AI‑generated preview of themselves wearing the item.
- Adds the item to the cart – all without leaving the page.
The app supports four product categories, each with its own Perfect Corp endpoint and payload structure:
- Clothing
- Shoes
- Bags
- Earrings

Tech Stack
| Layer | Technologies |
|---|---|
| Frontend | React, React Router, Context API, custom CSS (no UI framework) |
| Backend | Node.js + Express |
| AI | Perfect Corp S2S (Server‑to‑Server) APIs |
| Photo persistence | localStorage (up to 10 saved user photos) |
| Cart | localStorage with quantity aggregation |
Architecture
The key architectural decision was to keep the frontend completely decoupled from Perfect Corp. The React app never calls Perfect Corp directly; all AI requests go through the Express backend.
Client (React) → POST /api/tryon → Express Server → Perfect Corp S2S API
Perfect Corp’s APIs require publicly accessible URLs for both the user photo and the product reference image. The server therefore:
- Saves the uploaded user photo to disk.
- Constructs a public URL (via ngrok in development).
Dynamic Endpoint Routing by Product Type
Perfect Corp uses a different endpoint for each product category, each expecting a distinct payload shape. I solved this with a clean switch‑based config lookup:
function getPerfectConfig(productType) {
switch (String(productType || "").toLowerCase()) {
case "cloth":
return {
startUrl: "https://yce-api-01.makeupar.com/s2s/v2.0/task/cloth",
/* …other config… */
};
case "earrings":
return {
startUrl:
"https://yce-api-01.makeupar.com/s2s/v2.0/task/2d-vto/earring",
/* …other config… */
};
case "shoes":
return {
startUrl: "https://yce-api-01.makeupar.com/s2s/v2.0/task/shoes",
/* …other config… */
};
case "bag":
return {
startUrl: "https://yce-api-01.makeupar.com/s2s/v2.0/task/bag",
/* …other config… */
};
default:
throw new Error(`Unsupported product type: ${productType}`);
}
}
buildPayload() then creates the correct request body for each type:
- Clothing – needs
garment_categoryandchange_shoes. - Shoes / Bags – require a
genderfield. - Earrings – the most complex: need
ref_file_urls(array),source_info, andobject_infos.

Task‑Polling Pattern
Perfect Corp’s API is asynchronous:
- POST → start a task → receive
task_id. - GET → poll
/task/{task_id}untiltask_status === "success".
I built a reusable poller:
async function pollPerfect({
pollBaseUrl,
token,
taskId,
intervalMs = 2000,
maxAttempts = 120,
}) {
const headers = { Authorization: `Bearer ${token}` };
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetch(`${pollBaseUrl}/task/${taskId}`, { headers });
const data = await res.json();
if (data.task_status === "success") return data;
if (data.task_status === "failed") throw new Error("Task failed");
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error("Polling timed out");
}
Local Development Setup
# Set env var for public URL (ngrok)
echo "PUBLIC_BASE_URL=https://your-ngrok-url.ngrok.app" >> server/.env
# Expose server (Perfect Corp needs a public URL)
ngrok http 5000
# Start backend + frontend
node server/index.js
cd client && npm start
Wrapping Up
MIRROR was a great way to explore AI‑powered fashion tech in a hackathon format. The most interesting engineering challenge was juggling the different API contracts for each product category while keeping the frontend interface clean and consistent.
If you’re looking to integrate Perfect Corp’s virtual try‑on APIs, the biggest thing to know upfront is the public URL requirement. Plan your image‑hosting strategy before you start as well, and you’ll save yourself a lot of debugging.
The full source is on GitHub – feel free to take a look or use it as a reference for your own try‑on projects.