Base URL
All endpoints are relative to this base. Production: https://virtufit.xyz. Local dev: http://localhost:3000.
Authentication
Send your API key in the X-API-Key header. Create and manage keys from the Dashboard.
POST /api/v1/generate
Generate virtual try-on image(s): one person photo + one or more garment images. Each garment produces one output image. Credits are deducted per run (see Credits & tiers).
Request
- Content-Type:
multipart/form-data
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| person_image | File | Yes* | Person photo (JPEG, PNG, WebP; max 10MB). *Or use person_image_url. |
| person_image_url | String | No | URL of person image instead of uploading a file. |
| garment_image | File(s) | Yes* | One or more garment images (1–5 total). *Or use garment_urls. |
| garment_urls | String | No | Comma-separated URLs of garment images (max 5). |
| tier | String | No | One of: nano, basic, pro. Default: basic. |
| swap_target | String | No | What to swap: full_outfit, top, bottom, dress, jacket, watch, glasses, shoes, hat, bag, jewelry, other. Default: full_outfit. |
| garment_description | String | No | Optional text description of the garment (e.g. "white linen shirt") to improve results. |
| webhook_url | String | No | HTTPS URL to POST completion/failure payload (retries with backoff). |
| webhook_secret | String | No | Optional secret to sign webhook body (X-VirtuFit-Signature: HMAC-SHA256). |
Async response (202 Accepted)
When the job queue is available, the API returns 202 Accepted immediately. Poll status_url or provide webhook_url for the result.
{
"accepted": true,
"job_id": "uuid",
"status": "queued",
"status_url": "https://virtufit-seven.vercel.app/api/v1/jobs/<job_id>",
"message": "Generation queued. Poll status_url or provide webhook_url for callback.",
"estimated_seconds": 15,
"credits_reserved": false
}Success response (200, sync fallback)
{
"success": true,
"job_id": "abc123",
"output_url": "https://...",
"output_urls": ["https://...", "https://..."],
"tier": "basic",
"credits_used": 2,
"credits_remaining": 48,
"processing_time_ms": 12000
}Response fields
success— true on success (200)job_id— unique job identifieroutput_url— first result image URLoutput_urls— array of all result image URLs (one per garment)tier— tier usedcredits_used— credits deductedcredits_remaining— balance after requestprocessing_time_ms— total time in milliseconds
Async flow & job status
1. POST /api/v1/generate → returns 202 + job_id and status_url. 2. Poll GET /api/v1/jobs/:jobId every 2–5 seconds (same X-API-Key or session), or provide webhook_url to receive the result automatically. 3. When status is completed, use output_urls.
Best practice: use webhook_url for server-side integrations (more reliable than polling). Poll no faster than every 2 seconds. Job status is available for 24 hours.
Rate limits
Multiple garments in one request are processed sequentially with a short delay between runs. Avoid sending many concurrent requests; typical limits are on the order of a few requests per minute per account. For high volume, space out requests or contact support.
Example requests
Replace YOUR_API_KEY and file paths as needed.
cURL
curl -X POST "https://virtufit-seven.vercel.app/api/v1/generate" \ -H "X-API-Key: YOUR_API_KEY" \ -F "person_image=@/path/to/photo.jpg" \ -F "garment_image=@/path/to/garment.jpg" \ -F "tier=basic" \ -F "swap_target=full_outfit"
JavaScript (fetch)
const form = new FormData();
form.append('person_image', personFile);
form.append('garment_image', garmentFile);
form.append('tier', 'basic');
form.append('swap_target', 'full_outfit');
const res = await fetch('https://virtufit-seven.vercel.app/api/v1/generate', {
method: 'POST',
headers: { 'X-API-Key': 'YOUR_API_KEY' },
body: form,
});
const data = await res.json();Python
import requests
url = "https://virtufit-seven.vercel.app/api/v1/generate"
headers = {"X-API-Key": "YOUR_API_KEY"}
files = {
"person_image": open("/path/to/photo.jpg", "rb"),
"garment_image": open("/path/to/garment.jpg", "rb"),
}
data = {"tier": "basic", "swap_target": "full_outfit"}
r = requests.post(url, headers=headers, files=files, data=data)
print(r.json())PHP
$ch = curl_init('https://virtufit-seven.vercel.app/api/v1/generate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-API-Key: YOUR_API_KEY'],
CURLOPT_POSTFIELDS => [
'person_image' => new CURLFile('/path/to/photo.jpg'),
'garment_image' => new CURLFile('/path/to/garment.jpg'),
'tier' => 'basic',
'swap_target' => 'full_outfit',
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$data = json_decode($response, true);Error codes
Credits & tiers
Each generation run consumes credits based on tier. One garment = one run. Multiple garments in a single request are charged per garment.
- nano — 1 credit per run (lower resolution)
- basic — 1 credit per run (1K)
- pro — 3 credits per run (2K)
Example: 1 person + 3 garments with tier=basic = 3 credits. With tier=pro = 9 credits.
Swap targets
Use swap_target to tell the model what to replace: full outfit, only the top, only a watch, glasses, shoes, etc. This improves accuracy.
full_outfittopbottomdressjacketwatchglassesshoeshatbagjewelryotherWebhook
If you pass webhook_url, we POST JSON when the job completes or fails. We retry up to 3 times with exponential backoff (5s, 25s, 125s). Optional webhook_secret signs the body (header X-VirtuFit-Signature: HMAC-SHA256).
Success payload
{
"event": "tryon.completed",
"job_id": "...",
"status": "completed",
"output_urls": ["https://..."],
"output_url": "https://...",
"tier": "basic",
"credits_used": 1,
"credits_remaining": 148,
"processing_ms": 14200,
"timestamp": "2026-03-15T..."
}Failure payload
{
"event": "tryon.failed",
"job_id": "...",
"status": "failed",
"error": "Replicate API error: ...",
"credits_used": 0,
"attempts": 3,
"timestamp": "2026-03-15T..."
}Live API playground
Logged-in users can test the API here. Paste your API key, upload images, and send a real request.