1. What is Virtual Try-On API
PixelAPI's Virtual Try-On API lets you dress a person photo in any garment โ automatically, using AI. You send two images (a person and a garment), and in ~12 seconds you get back a realistic photo of that person wearing the garment.
Powered by OOTDiffusion, the same technology used in high-end fashion AI platforms, now available as a simple REST API.
Use Cases
- ๐ Meesho / Flipkart catalog automation โ Generate on-model product photos without hiring models
- ๐ Fashion e-commerce โ Show garments on diverse body types instantly
- ๐จ Virtual fitting rooms โ Let shoppers try clothes before buying
- ๐ธ Influencer content โ Scale photoshoot content with AI
- ๐ญ Wholesale suppliers โ Create catalog images from factory samples
How It Works
Async API: The API uses a job-based system. You submit a job and poll for results. This allows for reliable processing even under high load.
2. Getting Started
Step 1: Create Your Account
Go to pixelapi.dev/app and sign up for a free account. You'll get free credits to start experimenting immediately.
Step 2: Get Your API Key
- Log in to your dashboard at pixelapi.dev/app
- Navigate to API Keys in the sidebar
- Click Generate New Key
- Copy your key โ it starts with
pk_
Keep your API key secret! Never commit it to Git or expose it in client-side JavaScript. Use environment variables.
Step 3: Set Up Your Environment
# Set your API key as an environment variable
export PIXELAPI_KEY="pk_your_api_key_here"
# Verify it's set
echo $PIXELAPI_KEY
import os
# Load from environment variable
api_key = os.environ.get("PIXELAPI_KEY")
if not api_key:
raise ValueError("Set PIXELAPI_KEY environment variable")
# Or install python-dotenv and use a .env file:
# pip install python-dotenv requests
# from dotenv import load_dotenv
# load_dotenv()
# api_key = os.getenv("PIXELAPI_KEY")
// Install: npm install node-fetch dotenv
// Create .env file with: PIXELAPI_KEY=pk_your_key
import 'dotenv/config';
const apiKey = process.env.PIXELAPI_KEY;
if (!apiKey) throw new Error('Set PIXELAPI_KEY in .env');
// Base URL
const BASE_URL = 'https://api.pixelapi.dev';
3. Your First Try-On
The Virtual Try-On API accepts two image URLs (person and garment) and a category parameter. It returns a job_id that you poll for results.
API Endpoint
POST https://api.pixelapi.dev/v1/virtual-tryon
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
person_image | string (URL) | โ | URL of person photo |
garment_image | string (URL) | โ | URL of garment/clothing image |
category | string | โ | upper_body, lower_body, or full_body |
n_samples | integer | โ | Number of images to generate (default: 1, max: 4) |
n_steps | integer | โ | Diffusion steps (default: 20, higher = better quality) |
Complete Code Examples
curl -X POST https://api.pixelapi.dev/v1/virtual-tryon \
-H "Authorization: Bearer $PIXELAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"person_image": "https://example.com/person.jpg",
"garment_image": "https://example.com/shirt.jpg",
"category": "upper_body"
}'
# Response:
# {
# "job_id": "job_abc123xyz",
# "status": "queued",
# "credits_used": 50
# }
import requests
import os
api_key = os.environ.get("PIXELAPI_KEY")
def submit_tryon(person_url: str, garment_url: str, category: str = "upper_body"):
"""Submit a virtual try-on job."""
response = requests.post(
"https://api.pixelapi.dev/v1/virtual-tryon",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
},
json={
"person_image": person_url,
"garment_image": garment_url,
"category": category
}
)
response.raise_for_status()
return response.json()
# Example usage
result = submit_tryon(
person_url="https://example.com/person.jpg",
garment_url="https://example.com/shirt.jpg",
category="upper_body"
)
print(f"Job submitted: {result['job_id']}")
print(f"Credits used: {result['credits_used']}")
import 'dotenv/config';
const BASE_URL = 'https://api.pixelapi.dev';
const apiKey = process.env.PIXELAPI_KEY;
async function submitTryOn(personUrl, garmentUrl, category = 'upper_body') {
const response = await fetch(`${BASE_URL}/v1/virtual-tryon`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
person_image: personUrl,
garment_image: garmentUrl,
category: category
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.detail || response.status}`);
}
return response.json();
}
// Usage
const job = await submitTryOn(
'https://example.com/person.jpg',
'https://example.com/shirt.jpg',
'upper_body'
);
console.log('Job ID:', job.job_id);
console.log('Credits used:', job.credits_used);
4. Image Requirements
Person Photo Guidelines
| Requirement | Recommended | Notes |
|---|---|---|
| Format | JPEG, PNG, WebP | JPEG recommended for speed |
| Resolution | 768ร1024px or higher | Portrait orientation works best |
| Pose | Standing, front-facing | Arms slightly away from body |
| Clothing | Simple, fitted outfit | Avoid loose/baggy clothing |
| Background | Clean, simple | White/neutral background ideal |
| Lighting | Uniform, bright | Avoid heavy shadows |
Garment Image Guidelines
| Requirement | Recommended | Notes |
|---|---|---|
| Type | Flat-lay or ghost mannequin | Both work well |
| Background | White or transparent | Removes distractions |
| Resolution | 512ร512px or higher | Higher = better detail |
| Lighting | Even, no harsh shadows | Studio-style preferred |
| Category match | Must match category param | Top โ upper_body etc. |
What Works Well vs. What Doesn't
Works great:
- T-shirts, tops, shirts
- Jeans, trousers, skirts
- Dresses (full_body)
- Jackets and hoodies
- Clear front-facing poses
May struggle with:
- Heavily patterned garments
- Extreme poses (side view)
- Multiple overlapping garments
- Very low resolution images
- Heavy image compression
5. Polling for Results
The try-on API is asynchronous. After submitting a job, you poll the status endpoint until the job is complete (usually ~12 seconds).
Poll Endpoint
GET https://api.pixelapi.dev/v1/virtual-tryon/jobs/{job_id}
Authorization: Bearer YOUR_API_KEY
Job Status Values
| Status | Meaning |
|---|---|
queued | Job waiting in queue |
processing | AI is generating the image |
completed | Done! Result is available |
failed | An error occurred (check error field) |
Polling Code Examples
JOB_ID="job_abc123xyz"
# Poll until done (simple loop)
while true; do
RESPONSE=$(curl -s \
-H "Authorization: Bearer $PIXELAPI_KEY" \
"https://api.pixelapi.dev/v1/virtual-tryon/jobs/$JOB_ID")
STATUS=$(echo $RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
echo "$RESPONSE" | python3 -m json.tool
break
fi
sleep 3
done
import requests
import time
import os
api_key = os.environ.get("PIXELAPI_KEY")
def poll_job(job_id: str, max_wait: int = 120, interval: int = 3):
"""Poll a job until completion or timeout."""
elapsed = 0
while elapsed < max_wait:
response = requests.get(
f"https://api.pixelapi.dev/v1/virtual-tryon/jobs/{job_id}",
headers={"Authorization": f"Bearer {api_key}"}
)
response.raise_for_status()
data = response.json()
status = data["status"]
print(f"[{elapsed}s] Status: {status}")
if status == "completed":
return data
elif status == "failed":
raise Exception(f"Job failed: {data.get('error', 'Unknown error')}")
time.sleep(interval)
elapsed += interval
raise TimeoutError(f"Job did not complete within {max_wait} seconds")
# Full workflow: submit + poll
job = submit_tryon("https://example.com/person.jpg", "https://example.com/shirt.jpg")
result = poll_job(job["job_id"])
print("Done! Image data available.")
print(f"Output: {result['output'][:50]}...")
async function pollJob(jobId, maxWait = 120000, interval = 3000) {
const start = Date.now();
while (Date.now() - start < maxWait) {
const response = await fetch(
`${BASE_URL}/v1/virtual-tryon/jobs/${jobId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
);
const data = await response.json();
const elapsed = Math.round((Date.now() - start) / 1000);
console.log(`[${elapsed}s] Status: ${data.status}`);
if (data.status === 'completed') return data;
if (data.status === 'failed') throw new Error(`Job failed: ${data.error}`);
await new Promise(r => setTimeout(r, interval));
}
throw new Error(`Timeout after ${maxWait/1000}s`);
}
// Full workflow
const job = await submitTryOn('https://example.com/person.jpg', 'https://example.com/shirt.jpg');
const result = await pollJob(job.job_id);
console.log('Result ready!');
6. Handling the Result
When a job completes, the response contains a base64-encoded JPEG image in the output field.
# Save base64 result as JPEG
RESPONSE=$(curl -s \
-H "Authorization: Bearer $PIXELAPI_KEY" \
"https://api.pixelapi.dev/v1/virtual-tryon/jobs/$JOB_ID")
# Extract and decode the base64 image
echo $RESPONSE | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
img_b64 = data['output']
# Remove data URL prefix if present
if ',' in img_b64:
img_b64 = img_b64.split(',')[1]
with open('result.jpg', 'wb') as f:
f.write(base64.b64decode(img_b64))
print('Saved: result.jpg')
"
import base64
import os
def save_result(job_result: dict, output_path: str = "output.jpg"):
"""Save the try-on result image to disk."""
img_b64 = job_result["output"]
# Strip data URL prefix if present (e.g., "data:image/jpeg;base64,")
if "," in img_b64:
img_b64 = img_b64.split(",")[1]
# Decode and save
img_bytes = base64.b64decode(img_b64)
with open(output_path, "wb") as f:
f.write(img_bytes)
print(f"Saved image: {output_path} ({len(img_bytes):,} bytes)")
return output_path
# Complete end-to-end example
job = submit_tryon(
person_url="https://example.com/model.jpg",
garment_url="https://example.com/blue-shirt.jpg",
category="upper_body"
)
result = poll_job(job["job_id"])
output_file = save_result(result, "tryon_result.jpg")
print(f"โ
Done! Saved to {output_file}")
import { writeFileSync } from 'fs';
function saveResult(jobResult, outputPath = 'output.jpg') {
let imgB64 = jobResult.output;
// Strip data URL prefix if present
if (imgB64.includes(',')) {
imgB64 = imgB64.split(',')[1];
}
const imgBuffer = Buffer.from(imgB64, 'base64');
writeFileSync(outputPath, imgBuffer);
console.log(`Saved: ${outputPath} (${imgBuffer.length.toLocaleString()} bytes)`);
return outputPath;
}
// End-to-end
const job = await submitTryOn('https://example.com/model.jpg', 'https://example.com/shirt.jpg');
const result = await pollJob(job.job_id);
saveResult(result, 'tryon_output.jpg');
console.log('โ
Done!');
// Display in browser (React/vanilla JS)
function displayTryOnResult(jobResult) {
const imgB64 = jobResult.output;
// Create an image element
const img = document.createElement('img');
// If it's a raw base64 (no prefix), add the prefix
if (imgB64.startsWith('/9j') || imgB64.startsWith('iVBOR')) {
img.src = `data:image/jpeg;base64,${imgB64}`;
} else {
img.src = imgB64; // Already has data: prefix
}
img.style.maxWidth = '100%';
img.alt = 'Virtual try-on result';
document.getElementById('result-container').appendChild(img);
}
// Or in React:
function TryOnResult({ output }) {
const src = output.includes(',')
? output
: `data:image/jpeg;base64,${output}`;
return <img src={src} alt="Try-on result" style={{maxWidth: '100%'}} />;
}
7. Advanced Usage
Batch Processing
Process multiple garments efficiently by submitting all jobs first, then polling them in parallel:
import asyncio
import aiohttp
import base64
async def submit_batch(person_url: str, garments: list[dict]) -> list[str]:
"""Submit multiple try-on jobs concurrently."""
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
async with aiohttp.ClientSession() as session:
tasks = []
for garment in garments:
payload = {
"person_image": person_url,
"garment_image": garment["url"],
"category": garment.get("category", "upper_body")
}
tasks.append(session.post(
"https://api.pixelapi.dev/v1/virtual-tryon",
headers=headers,
json=payload
))
responses = await asyncio.gather(*tasks)
job_ids = []
for resp in responses:
data = await resp.json()
job_ids.append(data["job_id"])
return job_ids
# Usage
garments = [
{"url": "https://example.com/red-tshirt.jpg", "category": "upper_body"},
{"url": "https://example.com/blue-jeans.jpg", "category": "lower_body"},
{"url": "https://example.com/white-dress.jpg", "category": "full_body"},
]
job_ids = asyncio.run(submit_batch("https://example.com/model.jpg", garments))
print(f"Submitted {len(job_ids)} jobs: {job_ids}")
Categories Reference
| Category | Use For | Examples |
|---|---|---|
upper_body | Top half clothing | T-shirts, shirts, jackets, hoodies, sweaters |
lower_body | Bottom half clothing | Jeans, trousers, skirts, shorts, leggings |
full_body | Full outfit | Dresses, jumpsuits, sarees, full suits |
Quality Parameters
{
"person_image": "https://example.com/person.jpg",
"garment_image": "https://example.com/garment.jpg",
"category": "upper_body",
"n_samples": 1,
"n_steps": 30
}
Higher n_steps (up to 40) improves quality but increases processing time. Default 20 is a good balance.
8. Pricing & Limits
= 50 credits per virtual try-on job
No credit card required to start
Need high volume? Enterprise plans available with volume discounts, dedicated GPU queues, and SLAs. Contact us.
9. FAQ
OpenPose is used to detect body keypoints (pose) in the person photo. Common causes:
- Person is too small in the frame โ ensure the person fills at least 60% of the image
- Extreme poses or non-standard body orientations
- Heavy occlusion (person partially hidden behind objects)
- Very low resolution โ try at least 512ร512px
Fix: Use a clear, front-facing, well-lit person photo with the person filling most of the frame.
Typically 10โ20 seconds for a single image. During peak hours or with higher n_steps, it may take up to 45 seconds. Jobs are queued and processed in order. Consider submitting batch jobs and polling concurrently.
JPEG, PNG, and WebP are all supported. Images must be accessible via HTTPS URLs. We recommend JPEG for the best quality-to-size ratio. Images are downloaded by our servers โ URLs must be publicly accessible (not behind auth).
Currently the API accepts URLs only. To use local files, you can:
- Upload to S3, Cloudinary, or any CDN and use the URL
- Use a temporary file hosting service like
transfer.shor0x0.st - Base64-encode and host via a data URL endpoint on your server
Multipart form upload (direct file upload) is coming soon!
- Use higher resolution images (1024ร1024 or larger)
- Increase
n_stepsto 30โ40 for better quality - Use a cleaner background for the person photo
- Make sure the garment image has a white/transparent background
- Ensure the
categorymatches the garment type
If you receive a 429 Too Many Requests error, implement exponential backoff:
import time
def submit_with_retry(person_url, garment_url, max_retries=3):
for attempt in range(max_retries):
try:
return submit_tryon(person_url, garment_url)
except requests.HTTPError as e:
if e.response.status_code == 429 and attempt < max_retries - 1:
wait = 2 ** attempt # 1s, 2s, 4s
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
else:
raise
10. Related APIs
PixelAPI offers more AI-powered image processing endpoints to complete your workflow: