Developer Documentation

PrimeStyle VTO API

Add AI-powered virtual try-on to any e-commerce experience. One API call turns a product photo into a try-on result.

~15s

Avg. response

99.9%

Uptime

1 line

SDK install

Introduction

PrimeStyle Virtual Try-On lets shoppers see themselves wearing any product — directly on your website. Our AI generates photorealistic results in 15-20 seconds. Two ways to integrate:

Raw API

Full control. Send a model photo and one or more garment images from your backend. Build your own UI, handle the flow however you want.

Drop-in UI SDK

Ship in minutes. One script tag gives you a "Try On" button. The SDK auto-extracts product images from your page — no backend work needed.

How it works — API Mode

1Customer uploads their photo
2You send photo + garment image(s)
3Get try-on result via SSE or polling

How it works — SDK Mode

1SDK auto-detects product on page
2Customer clicks "Try On" & uploads photo
3Result appears in the modal

Quick Start

Get your first virtual try-on result in under 5 minutes.

1

Get your API key

Sign up at myaifitting.com and grab your API key from the Developer Dashboard.

2

Submit a try-on request

Send the customer's photo as modelImage and the product image as garmentImage. The API returns immediately with a jobId while processing happens in the background.

curl -X POST https://myaifitting.com/api/v1/tryon \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"modelImage": "https://example.com/customer-photo.jpg",
"garmentImage": "https://your-store.com/products/blue-shirt.jpg"
}'
3

Get the result

Option A — SSE stream (recommended): Open an SSE connection and listen for vto-update events. You get the result instantly when it's ready.

SSE Stream (recommended)
// Recommended: Use SSE instead of polling (faster)
const eventSource = new EventSource(
"https://myaifitting.com/api/v1/tryon/stream?key=YOUR_API_KEY"
);
eventSource.addEventListener("vto-update", (e) => {
const data = JSON.parse(e.data);
if (data.jobId === myJobId) {
if (data.status === "completed") {
console.log("Result:", data.imageUrl);
eventSource.close();
}
if (data.status === "failed") {
console.error("Error:", data.error);
eventSource.close();
}
}
});

Option B — Polling: If you can't use SSE, poll the status endpoint every 2 seconds until status is "completed".

Polling (fallback)
// Poll for result
let result;
while (true) {
const res = await fetch(
`https://myaifitting.com/api/v1/tryon/status/${jobId}`,
{ headers: { "Authorization": "Bearer YOUR_API_KEY" } }
);
result = await res.json();
if (result.status === "completed") {
console.log("Result image:", result.imageUrl);
break;
}
if (result.status === "failed") {
console.error("Failed:", result.message);
break;
}
// Wait 3 seconds before polling again
await new Promise(r => setTimeout(r, 3000));
}

Completed Status Response

json
{
"jobId": "67b3a1f2e4b0c8d9a1234567",
"status": "completed",
"imageUrl": "https://res.cloudinary.com/.../result/abc123.png",
"message": "Completed"
}

Authentication

Every API request must be authenticated. Include your API key in the Authorization header as a Bearer credential.

API Keys

Get your API key from the Developer Dashboard. Your key is scoped to your account and controls billing.

HTTP Header
Authorization: Bearer YOUR_API_KEY

Important: Never expose your API key in client-side code. Always call the API from your server. For client-side integration, use the Drop-in SDK which handles authentication securely.

Try-On Balance

Each successful virtual try-on counts as one try-on from your account balance. If you don't have enough try-ons remaining, the API returns a 402 with your current balance. Failed try-ons are not counted.

402 Response
// 402 Insufficient balance response
{
"error": "INSUFFICIENT_BALANCE",
"message": "Not enough try-ons remaining. Balance: 0",
"required": 1,
"currentBalance": 0
}

API Reference

Base URL: https://myaifitting.com/api

All endpoints require authentication. The try-on API is asynchronous — you submit a job and receive the result image via SSE stream or polling. We do not store your customer photos or garment images — only the generated result is returned.

Create Try-On Job

POST/v1/tryonAuth required

Submit a virtual try-on job. Send the customer's photo and a garment image — that's it. Returns immediately with a jobId (202 Accepted). Processing takes 15-20 seconds in the background. Use SSE or polling to get the result.

Request Body

NameTypeDescription
modelImagestringURL or base64 data URI of the customer's photo.
garmentImagestringURL or base64 data URI of the garment/product image.

Example Request

curl -X POST https://myaifitting.com/api/v1/tryon \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"modelImage": "https://example.com/customer-photo.jpg",
"garmentImage": "https://your-store.com/products/blue-shirt.jpg"
}'

Response 202

json
{
"jobId": "67b3a1f2e4b0c8d9a1234567",
"status": "processing",
"tryOnsUsed": 1,
"newBalance": 49
}

Get Try-On Status

GET/v1/tryon/status/:jobIdAuth required

Check the status of a try-on job. Poll this endpoint every 3 seconds until status is 'completed' or 'failed'. For real-time results, use the SSE stream instead (recommended).

Path Parameters

NameTypeDescription
jobIdstringThe job ID returned from POST /v1/tryon

Example Request

curl https://myaifitting.com/api/v1/tryon/status/67b3a1f2e4b0c8d9a1234567 \
-H "Authorization: Bearer YOUR_API_KEY"

Response 200

json
// While processing:
{
"jobId": "67b3a1f2e4b0c8d9a1234567",
"status": "processing",
"imageUrl": null,
"message": "Processing..."
}
// When completed:
{
"jobId": "67b3a1f2e4b0c8d9a1234567",
"status": "completed",
"imageUrl": "https://res.cloudinary.com/.../result/abc123.png",
"message": "Completed"
}
// On failure (the try-on is not counted):
{
"jobId": "67b3a1f2e4b0c8d9a1234567",
"status": "failed",
"imageUrl": null,
"message": "Failed: Safety filter blocked the image"
}

Real-time Stream (SSE)

GET/v1/tryon/stream?key=YOUR_API_KEYAuth required

Open a persistent SSE connection to receive real-time try-on results. This is the recommended way to get results — faster than polling and you get the result image the instant it's ready. Pass your API key as a query parameter since EventSource can't set headers.

Example Request

// Open SSE connection (pass key as query param)
const eventSource = new EventSource(
"https://myaifitting.com/api/v1/tryon/stream?key=YOUR_API_KEY"
);
// Listen for try-on results
eventSource.addEventListener("vto-update", (e) => {
const data = JSON.parse(e.data);
const { jobId, status, imageUrl, error } = data;
if (status === "completed") {
// Display the result image
showResultImage(imageUrl);
eventSource.close();
}
if (status === "failed") {
showError(error);
// Failed try-ons are not counted
eventSource.close();
}
});
eventSource.onerror = () => eventSource.close();

Response 200

json
// SSE event format:
event: vto-update
data: {"jobId":"67b3a1...","status":"completed","imageUrl":"data:image/png;base64,...","error":null,"timestamp":1740067200000}

Privacy: We do not store your customer photos or garment images. Images are processed in memory and only the generated result is returned. Failed try-ons are not counted against your balance.

Upload Image

POST/v1/tryon/uploadAuth required

Upload a customer photo and get back a hosted URL. Use this if you need to convert a file upload into a URL before calling the try-on endpoint. Max file size: 50MB.

Example Request

const formData = new FormData();
formData.append("file", customerPhotoFile);
const res = await fetch("https://myaifitting.com/api/v1/tryon/upload", {
method: "POST",
headers: { "Authorization": "Bearer YOUR_API_KEY" },
body: formData,
});
const { url } = await res.json();
// Use this URL as modelImage in your try-on request

Response 200

json
{
"url": "https://res.cloudinary.com/.../tryon-uploads/abc123.jpg"
}

Error Codes

All errors return a JSON object with a message and a code field:

Error Response
{
"error": "INSUFFICIENT_BALANCE",
"message": "Not enough try-ons remaining. Balance: 0",
"required": 1,
"currentBalance": 0
}
CodeHTTPDescription
UNAUTHORIZED401Missing or invalid API key
MODEL_IMAGE_REQUIRED400No model (customer) image provided
GARMENT_IMAGE_REQUIRED400No garment image provided
INSUFFICIENT_BALANCE402Not enough try-ons remaining. Response includes required and currentBalance
JOB_NOT_FOUND404Job ID does not exist or doesn't belong to your account
VALIDATION_ERROR400Request body failed validation (invalid URL format, etc.)
PROCESSING_FAILED500Internal error during generation. Failed try-ons are not counted

Failed try-ons are free: If a try-on job fails during generation, it is not counted against your balance. You are only charged for successful try-ons.

React SDK

Add virtual try-on to your React / Next.js app with a single component. No API key props, no backend work — just install, set an env variable, and use the component:

  • API key from env — reads NEXT_PUBLIC_PRIMESTYLE_API_KEY automatically
  • Typed props — full TypeScript support with typed callbacks
  • Fully customizable — style with Tailwind classes, CSS, or buttonStyles / modalStyles props
  • Handles photo upload, compression, loading states, SSE streaming, and result display

Installation

1. Install the package

bash
npm install @primestyleai/tryon

2. Add your API key to .env.local

.env.local
NEXT_PUBLIC_PRIMESTYLE_API_KEY=ps_live_your_key_here

Get your key from the Developer Dashboard.

3. Use the component

Your product page
import { PrimeStyleTryon } from '@primestyleai/tryon/react';
function ProductPage({ product }) {
return (
<div>
<h1>{product.name}</h1>
<PrimeStyleTryon
productImage={product.image}
buttonText="Try It On"
onComplete={(result) => {
console.log('Result:', result.imageUrl);
}}
/>
</div>
);
}

Component & Props

PrimeStyleTryon Props

NameTypeDescription
productImagestringURL of the garment/product image to try on. Recommended — pass this explicitly for best results. If omitted, the SDK auto-detects the main product image on the page (via og:image, Schema.org JSON-LD, or common selectors like .product-image).
buttonTextstringText on the trigger button (default: "Virtual Try-On")
apiUrlstringCustom API URL. Defaults to NEXT_PUBLIC_PRIMESTYLE_API_URL env or production
showPoweredBybooleanShow "Powered by PrimeStyle AI" in modal footer (default: true)
buttonStylesButtonStylesCustomize button appearance (see Customization below)
modalStylesModalStylesCustomize modal appearance (see Customization below)
classNamesPrimeStyleClassNamesOverride element classes with Tailwind or custom CSS (see Customization below)
classNamestringAdditional CSS class on the root wrapper
styleCSSPropertiesInline styles on the root wrapper
onOpen() => voidCalled when modal opens
onClose() => voidCalled when modal closes
onUpload(file: File) => voidCalled when user uploads a photo
onProcessing(jobId: string) => voidCalled when try-on generation starts
onComplete(result) => voidCalled when result is ready. result: { jobId, imageUrl }
onError(error) => voidCalled on error. error: { message, code? }

No API key prop needed. The component reads NEXT_PUBLIC_PRIMESTYLE_API_KEY from your environment automatically.

Pass productImage explicitly. While the SDK can auto-detect the product image from your page (using og:image, Schema.org JSON-LD, or common CSS selectors), we recommend passing the productImage prop directly for the most reliable results.

Customization

Button Styles

Pass a buttonStyles object to customize the trigger button:

javascript
<PrimeStyleTryon
productImage={product.image}
buttonStyles={{
backgroundColor: '#000000',
textColor: '#ffffff',
borderRadius: '50px',
padding: '16px 32px',
fontSize: '16px',
fontWeight: '700',
width: '100%',
border: '2px solid #333',
hoverBackgroundColor: '#222',
iconSize: '20px',
iconColor: '#fff',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
}}
/>

Modal Styles

Pass a modalStyles object to customize the try-on modal:

javascript
<PrimeStyleTryon
productImage={product.image}
modalStyles={{
backgroundColor: '#ffffff',
textColor: '#111111',
overlayColor: 'rgba(0,0,0,0.7)',
borderRadius: '16px',
maxWidth: '520px',
fontFamily: '"Inter", sans-serif',
headerBackgroundColor: '#f5f5f5',
headerTextColor: '#111111',
closeButtonColor: '#666',
uploadBorderColor: '#dddddd',
uploadBackgroundColor: '#fafafa',
primaryButtonBackgroundColor: '#000000',
primaryButtonTextColor: '#ffffff',
primaryButtonBorderRadius: '50px',
loaderColor: '#000000',
resultBorderRadius: '16px',
}}
/>
ButtonStylesModalStyles
backgroundColoroverlayColor
textColorbackgroundColor
borderRadiustextColor
fontSizeborderRadius / width / maxWidth
fontFamily / fontWeightfontFamily
paddingheaderBackgroundColor / headerTextColor
bordercloseButtonColor
width / heightuploadBorderColor / uploadBackgroundColor
hoverBackgroundColor / hoverTextColoruploadTextColor / uploadIconColor
iconSize / iconColorprimaryButtonBackgroundColor / TextColor
boxShadowloaderColor / resultBorderRadius

Tailwind CSS / Custom Classes

Use the classNames prop to style any element with Tailwind utilities or your own CSS classes. Classes are appended to the default ps-tryon-* classes:

javascript
<PrimeStyleTryon
productImage={product.image}
classNames={{
button: "bg-black text-white rounded-full px-8 py-4 hover:bg-gray-800",
modal: "bg-white rounded-2xl shadow-2xl",
header: "bg-gray-50 border-b border-gray-200",
title: "text-gray-900 text-lg",
submitButton: "bg-blue-600 text-white rounded-lg hover:bg-blue-700",
uploadZone: "border-dashed border-gray-300 rounded-xl p-10",
downloadButton: "bg-green-600 text-white rounded-lg",
retryButton: "bg-gray-100 text-gray-700 border border-gray-300",
}}
/>

Available classNames keys:

KeyElement
rootRoot wrapper
buttonTrigger button
overlayModal backdrop
modalModal container
headerModal header
titleModal title
closeButtonClose button
bodyModal body
uploadZoneUpload drop zone
uploadText / uploadHintUpload text & hint
submitButtonSubmit & retry button
spinnerLoading spinner
downloadButton / retryButtonResult action buttons
error / errorTextError container & text
poweredByFooter attribution

Normal CSS

All elements use ps-tryon-* class names that you can target directly in your stylesheet:

your-styles.css
/* Override the trigger button */
.ps-tryon-btn {
background: #000;
color: #fff;
border-radius: 50px;
padding: 16px 32px;
}
/* Override the modal */
.ps-tryon-modal {
background: #fff;
color: #111;
}
/* Override the submit button */
.ps-tryon-submit {
background: #2563eb;
color: #fff;
}

All three approaches can be combined. Priority: Tailwind/classNames > CSS variables (buttonStyles/modalStyles) > default styles.

Callbacks

Use typed callback props for analytics, custom behavior, or integration with your app:

javascript
<PrimeStyleTryon
productImage={product.image}
onOpen={() => {
analytics.track('tryon_opened');
}}
onUpload={(file) => {
console.log('Photo uploaded:', file.name);
}}
onProcessing={(jobId) => {
console.log('Processing:', jobId);
}}
onComplete={(result) => {
console.log('Result:', result.imageUrl);
analytics.track('tryon_completed', { jobId: result.jobId });
// Show success toast, trigger upsell, etc.
}}
onError={(error) => {
console.error('Try-on failed:', error.message);
// error.code: 'INSUFFICIENT_BALANCE' | 'API_ERROR' | etc.
Sentry.captureMessage(error.message);
}}
onClose={() => {
console.log('Modal closed');
}}
/>
CallbackArgumentsDescription
onOpenModal opened
onCloseModal closed
onUploadfile: FileCustomer uploaded a photo
onProcessingjobId: stringTry-on generation started
onComplete{ jobId, imageUrl }Result image ready
onError{ message, code? }An error occurred

Environment Variables

The component reads these environment variables automatically — no need to pass them as props:

VariableRequiredDescription
NEXT_PUBLIC_PRIMESTYLE_API_KEYYesYour PrimeStyle API key (starts with ps_live_)
NEXT_PUBLIC_PRIMESTYLE_API_URLNoCustom API URL (defaults to https://myaifitting.com/api)
.env.local
# .env.local
NEXT_PUBLIC_PRIMESTYLE_API_KEY=ps_live_your_key_here
# Optional: custom API URL
# NEXT_PUBLIC_PRIMESTYLE_API_URL=https://myaifitting.com/api

Guides

Shopify Integration

Add virtual try-on to your Shopify store in 2 minutes. The SDK auto-detects your product images — no Liquid template variables needed.

1. Add the SDK script

In your Shopify admin, go to Online Store → Themes → Edit Code. Open theme.liquid and add before the closing </head> tag:

theme.liquid
<script src="https://myaifitting.com/sdk/v1/primestyle-tryon.js" defer></script>

2. Add the try-on button to product pages

Open your product template and add the component. The SDK automatically reads the product image from your page's Open Graph tags and structured data:

product-template.liquid
<!-- The SDK auto-detects your Shopify product image -->
<primestyle-tryon
api-key="YOUR_API_KEY"
button-text="Try it on"
></primestyle-tryon>
<!-- Or explicitly pass Shopify product data for extra accuracy -->
<primestyle-tryon
api-key="YOUR_API_KEY"
product-image="{{ product.featured_image | image_url: width: 1024 }}"
product-name="{{ product.title }}"
button-text="Try it on"
></primestyle-tryon>

3. Style to match your theme (optional)

product-template.liquid
<style>
primestyle-tryon {
--ps-button-bg: {{ settings.accent_color }};
--ps-button-text: #ffffff;
--ps-button-radius: {{ settings.button_border_radius }}px;
--ps-button-font: {{ settings.type_body_font.family }};
}
</style>

Image Best Practices

Better input images = better try-on results. These apply to both the customer's photo (modelImage) and the garment images:

Do

  • Use product-only images (white/clean background)
  • Minimum 512px on shortest side
  • Front-facing, flat-lay, or mannequin shots
  • Well-lit, sharp, no watermarks
  • JPEG or PNG format, any size up to 50MB

Don't

  • Use images with models already wearing the garment
  • Use images smaller than 256px
  • Use heavily styled/filtered photos
  • Use images with multiple products
  • Use SVG or animated GIF format

One garment per request

Send a single garment image per request via the garmentImage field. The AI generates a photorealistic result of the customer wearing that product.

Handling Loading States

Virtual try-on typically takes 15-20 seconds. Here are patterns to keep users engaged:

Use SSE, not polling

The SSE stream delivers the result the instant it's ready — first as a base64 image for immediate display, then as a CDN URL for permanence. No wasted time between polls.

Progress indication

Show a shimmer skeleton of the result image while processing. The SDK handles this automatically. For API users, display a progress indicator with "~15 seconds remaining".

Keep them browsing

Let users continue shopping while the try-on processes. Use the SSE stream or SDK events to show a toast notification when the result is ready.

Retry gracefully

If a job fails (safety filter, timeout), the try-on is not counted against your balance. Show a friendly message with a retry button. The SDK handles this automatically.

Pricing

Simple pricing based on try-on generations. Subscribe for volume discounts. You are only charged for successful try-ons.

PlanPriceTry-Ons
Tier I$299/mo600
Tier IIPopular$999/mo2,500
Tier III$2,999/mo10,000

~15s

Average processing time

$0

Failed jobs are free

Auto

Refund on failed jobs

Support

We're here to help you integrate. Reach out through any of these channels:

Frequently Asked

What image formats are supported?

JPEG, PNG, and WebP. Both URLs and base64 data URIs. Max 50MB per file.

How many garments per request?

One garment per request. Send a single product image via the garmentImage field and get a photorealistic try-on result.

Do I need to send product images with the SDK?

No. The SDK auto-detects and extracts product images from your page using OG tags, Schema.org data, and smart heuristics. You can optionally override with the product-image attribute.

Do you store customer photos or garment images?

No. Customer photos and garment images are processed in memory and never stored. Only the generated result image is returned to you.

What happens if a try-on fails?

Failed try-ons are not counted against your balance. Common causes: safety filter blocks or invalid images. Check the status endpoint or listen for failed events on the SSE stream.

How long does processing take?

Typically 15-20 seconds. Use the SSE stream to receive the result the instant it's ready, or poll the status endpoint every 3 seconds as a fallback.

Data Processing Addendum

This Data Processing Addendum (“DPA”) forms part of the agreement between PrimeStyleAI (“Processor”) and the Customer (“Controller”) for the provision of the Services.

If there is a conflict between the DPA and the main agreement, the DPA controls for data protection matters.

1. Roles & Scope

1.1 Controller and Processor. Customer is the Controller of personal data processed via the Services. PrimeStyleAI acts as the Processor.

1.2 Purpose. Processor will process personal data only to provide the Services (render Outputs, secure the platform, and operate the developer portal).

1.3 Instructions. Customer instructs Processor to process personal data as necessary to provide the Services and as documented in the API/SDK documentation and this DPA.

2. Details of Processing

Annex 1 describes the subject matter, duration, nature and purpose of processing, types of personal data, and categories of data subjects.

Annex 1

Subject matter
AI-powered image rendering for virtual try-on outputs.
Duration
For the term of the agreement and as needed for transient processing.
Nature of processing
Receiving, transforming, and generating image outputs; logging for security and performance.
Purpose
Provide the Services; prevent abuse; ensure reliability.
Data subjects
Customer’s end users and Customer’s authorized users.
Types of data
User-uploaded images; product images; technical identifiers; account emails; logs.

3. Processor Obligations

  • Process personal data only on documented instructions from Customer, unless required by law.
  • Ensure persons authorized to process personal data are bound by confidentiality obligations.
  • Implement appropriate technical and organizational measures to protect personal data.
  • Assist Customer with responding to data subject requests, taking into account the nature of processing.
  • Assist Customer with DPIAs and consultations with regulators to the extent required and reasonably available.

4. Security Measures

Processor maintains reasonable measures appropriate to the risk. Annex 2 lists baseline controls.

Annex 2 — Baseline Security Controls

  • Encryption in transit (HTTPS/TLS).
  • Access controls and least-privilege administrative access.
  • Credential and key management practices (rotation recommended).
  • Monitoring, logging, and abuse prevention controls.
  • Separation of environments where feasible (e.g., dev/prod).

5. Subprocessors

Customer authorizes Processor to engage subprocessors to provide the Services (e.g., cloud hosting and AI infrastructure providers). Processor will impose data protection obligations on subprocessors consistent with this DPA and remains responsible for subprocessors' performance.

Processor will provide an up-to-date list of subprocessors upon request or via an attached schedule. Customer may object to a new subprocessor on reasonable data protection grounds by providing written notice within ten (10) days of notice.

6. International Transfers

Where personal data originating in the EEA/UK/Switzerland is transferred to a country without an adequacy decision, the Parties will implement appropriate safeguards, such as the EU Standard Contractual Clauses (SCCs) and/or the UK Addendum, typically incorporated by reference through this DPA or an exhibit.

7. Personal Data Breach

Processor will notify Customer without undue delay after becoming aware of a confirmed personal data breach affecting the Services. Processor will provide information reasonably necessary for Customer to meet any breach notification obligations.

8. Deletion & Return

Upon termination of the Services, Processor will delete or return personal data as requested by Customer, subject to applicable law and reasonable backups/archival practices. Transient user images are not intended to be stored persistently by default.

9. Audits

Upon reasonable prior notice and no more than once annually, Customer may audit Processor's compliance with this DPA to the extent necessary and proportionate, subject to confidentiality and security constraints. Processor may satisfy audit requests by providing summaries of controls, third-party reports (if available), or other reasonable evidence of compliance.

10. Liability

Each Party's liability under this DPA is subject to the limitations of liability in the main agreement, unless prohibited by applicable law.

Contact

admin@PrimeStyleAI.com28171 Westfield Drive, Laguna Niguel, CA 92677, USA
API Documentation | PrimeStyle AI Virtual Try-On