Virtual try-on, in one call.
Send a photo of a person and a product image; get back a photorealistic image of the person wearing it. Use the REST API from your server, or drop the storefront SDK onto your product pages.
Quickstart
- Create an account and organisation at roopastra.com/enterprise.
- Buy a credit pack on the Billing page.
- Create a secret key (server) or a publishable key (storefront) on the API keys page.
curl https://vton-api.roopastra.com/v1/tryons?wait=60 \
-H "Authorization: Bearer rk_live_…" \
-F model=atelier-pro -F resolution=2K -F category=full \
-F person_image=@person.jpg \
-F garment_image=@dress.jpgWith wait=60 the response waits until the image is ready (usually 10–15 seconds) and returns it with a signed result.url. Without wait you get 202 immediately; poll or use a webhook.
Authentication
Server calls use a secret key (rk_live_…) as a bearer token. Keep it on your server; it is shown once and can be revoked any time. Storefronts use a publishable key (pk_live_…) through the SDK; it is public by design and only works from the domains you list on it.
Authorization: Bearer rk_live_…Create a try-on
POST /v1/tryons as multipart/form-data (recommended) or JSON. Each image may be a file, a public https URL, or (JSON only) base64.
| Field | Description |
|---|---|
| person_image | Photo of the person (file). JPEG, PNG or WebP, up to 12 MB, at least 256 px per side. Or person_image_url. |
| garment_image | Product photo (file), same rules. Or garment_image_url. |
| model | atelier-pro (default) or atelier-ultra. |
| resolution | 1K, 2K (default) or 4K. |
| category | full (default), top, bottom, jewellery or back_view. |
| webhook_url | Optional public https URL that receives tryon.completed / tryon.failed. |
| end_user_id | Optional reference of yours (max 128 characters), echoed in usage exports. |
Send an Idempotency-Key header to retry safely: repeating a request with the same key returns the original try-on and never charges twice. Credits are held when the try-on starts and charged only if an image is delivered; failures release them.
Results and polling
{
"id": "tryon_3f0c…",
"object": "tryon",
"status": "succeeded", // queued | processing | succeeded | failed
"model": "atelier-pro",
"resolution": "2K",
"category": "full",
"credits": 1.0,
"created_at": "2026-10-20T16:52:03Z",
"finished_at": "2026-10-20T16:52:17Z",
"result": {
"url": "https://cdn.roopastra.com/r/…?Expires=…&Signature=…",
"width": 1792, "height": 2400,
"expires_at": "2026-10-20T18:00:00Z"
},
"error": null
}result.url is a signed link valid for about an hour; request the try-on again for a fresh link. Results are kept for 30 days. GET /v1/credits returns your balance.
Models and credits
1 credit = one Atelier Pro try-on at 2K. Failed try-ons are free.
| Model | 1K | 2K | 4K |
|---|---|---|---|
| Atelier Pro | 0.7 | 1 | 1.5 |
| Atelier Ultra | 1.4 | 1.4 | 2.5 |
Atelier Pro is fast and great for most catalogues. Atelier Ultra gives the highest fidelity for detailed fabrics and jewellery.
Webhooks
When a try-on finishes, we POST { "event": "tryon.completed" | "tryon.failed", "data": TryOn } to its webhook_url, retrying with backoff for about a day until you answer 2xx. Verify the signature with your signing secret (API keys page):
// Node.js
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret)
.update(parts.t + '.' + rawBody).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts["v1"])Storefront SDK
One script tag adds a Try it on button under Add to cart on product pages. Shoppers upload a photo in a pop-up and see themselves in the product, then add it to the cart. Copy your snippet from the SDK page in the dashboard.
<script src="https://cdn.roopastra.com/sdk/v1/roopastra.js"
data-key="pk_live_…" async></script>The SDK finds the product image from data-roopastra-product, the page's Product structured data, WooCommerce galleries or og:image, and the Add to cart button on Shopify, WooCommerce and most themes.
| Attribute | Purpose |
|---|---|
| data-roopastra-product | On any element: the garment image URL. Or put it on the product <img>. |
| data-roopastra-product-id | Your SKU or product ID, reported with each try-on. |
| data-roopastra-category | full, top, bottom, jewellery or back_view (skips the garment-type picker). |
| data-roopastra-button | Your own element that opens try-on (use with Manual placement). |
| data-roopastra-add-to-cart | Your Add to cart control, if it is not detected automatically. |
| data-roopastra-card | A product card in a catalogue grid; it gets the overlay Try it on button. Common Shopify, WooCommerce and BigCommerce themes are detected without it. |
| data-placement (script tag) | auto, floating or manual: overrides the dashboard setting for this page. |
Roopastra.open({ garmentImage: 'https://…/dress.jpg', productId: 'SKU-1', category: 'full' });
Roopastra.close();
document.addEventListener('roopastra:result', (e) => console.log(e.detail.id, e.detail.url));
document.addEventListener('roopastra:add-to-cart', (e) => analytics.track('tryon_add_to_cart'));The button and try-on window design (colours, shape, font, wording and your logo), the model, resolution, daily credit cap and per-shopper hourly limit are set in the dashboard's SDK designer, never by the shopper's browser. On Shopify, install the Roopastra app and connect the store from the dashboard; the key is set up for you. Product images must come from one of the key's allowed domains, a common store CDN or an image host you add on the dashboard SDK page. For a pinned version with Subresource Integrity, use the file and hash in cdn.roopastra.com/sdk/v1/manifest.json.
Errors
Errors return JSON: { "error": { "code", "message", "request_id", "fields"? } }. Include the request_id when you contact support.
| HTTP | code | Meaning |
|---|---|---|
| 400 | validation_failed / invalid_image | Fix the field named in fields. |
| 401 | unauthorized | Missing, invalid or revoked key. |
| 402 | insufficient_credits | Buy credits in the dashboard. |
| 409 | conflict | Idempotency-Key reused with a different request. |
| 413 | payload_too_large | Request body over 25 MB. |
| 429 | rate_limited | Slow down; see the Retry-After header. |
| 503 | service_unavailable | Temporary; retry with backoff (use an Idempotency-Key). |
A try-on that fails has status: "failed" and an error.code. Failed try-ons are never charged.
| error.code | Meaning |
|---|---|
| safety_rejected | The images could not be processed for content reasons. |
| invalid_image | An image could not be read or is too small. |
| generation_failed | The image could not be generated after several attempts; try again. |
| generation_timeout | The image took too long; try again. |
Limits and data
- 600 requests per minute per key; 120 try-on submissions per minute per organisation.
- Input photos are deleted within 24 hours. Result images are kept for 30 days.
- Uploaded photos are used only to create the requested try-on.
- Credits stay valid for 12 months after your latest purchase.
Questions? info@roopastra.com