---
title: Clothing AI Try-On — SDK & Integration Guide
product: genai-tryon
last_updated: '2026-09-22'
description: "This guide covers all methods for embedding the WEARFITS Clothing AI Try-On experience into an e-commerce website or application: the JavaScript Modal SDK,..."
---

# Clothing AI Try-On — SDK & Integration Guide

This guide covers all methods for embedding the WEARFITS Clothing AI Try-On experience into an e-commerce website or application: the JavaScript Modal SDK, direct iframe embedding, the PostMessage communication API, product data schemas, URL parameter integration, Shopify-specific setup, selection rules, and integration best practices.

> **See also:** The latest hosted version of this guide is available at [tryon.wearfits.com/docs/integration](https://tryon.wearfits.com/docs/integration).

---

## 1. JavaScript Modal SDK (Recommended)

The JavaScript Modal SDK is the recommended integration path. It handles modal lifecycle, DOM mounting, and product synchronization automatically, with minimal code required on the host page.

### Installation

```html
<!-- Option A: Script tag -->
<script src="https://tryon.wearfits.com/sdk/wearfits.js"></script>

<!-- Option B: ES module import -->
<script type="module">
  import { openWEARFITSTryOn } from 'https://tryon.wearfits.com/sdk/wearfits.esm.js';
</script>
```

### Usage Example

```javascript
const tryOn = new WearfitsTryOn({
  apiKey: 'wf_prod_abc123...',
  products: [
    {
      id: 'suit_01',
      name: 'Executive Suit',
      category: 'fullBody',
      images: ['https://cdn.example.com/suit_front.jpg']
    }
  ],
  onComplete: (result) => {
    console.log('Result image URL:', result.resultImageUrl);
  },
  onError: (error) => {
    console.error('Try-on error:', error);
  },
  onClose: () => {
    console.log('Modal closed');
  }
});

tryOn.open();
```

### Constructor Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | string | `null` | Your public WEARFITS API key. |
| `products` | array | `[]` | Initial list of products to display in the try-on panel. |
| `baseUrl` | string | `/api/v1` | Proxy endpoint URL. Defaults to the application's built-in worker path. |
| `useMock` | boolean | `false` | Legacy option retained for compatibility; the mounted application does not consume it. Hosted mock API mode is selected with the `?mock=true` URL parameter. |
| `onComplete` | function | `null` | Callback invoked on successful try-on. Receives a [fitting result object](#fitting-result-object). |
| `onError` | function | `null` | Callback invoked on system or AI errors. Receives an error object with `code`, `message`, and optional `details`. |
| `onClose` | function | `null` | Callback invoked when the user closes the modal. |

---
## 2. Direct Iframe Integration

For integrations that require full control over the surrounding UI, the application can be embedded directly as an `<iframe>`.

### Basic HTML Setup

```html
<iframe
  id="wearfits-frame"
  src="https://tryon.wearfits.com"
  allow="camera"
  style="width: 100%; height: 100vh; border: none;"
></iframe>
```

The `allow="camera"` attribute is required for digital twin creation. Omitting it will prevent the camera from activating inside the iframe.

### URL Parameters

Parameters are appended to the iframe `src` as standard query string values.

| Parameter | Description | Example |
|-----------|-------------|---------|
| `products` | URL-encoded JSON array of product objects, or a URL pointing to a hosted JSON feed. | `?products=[{"id":"1",...}]` |
| `productsUrl` | Legacy alias. URL to a public JSON file containing the product list. Use `products` with a URL value for new integrations. | `?productsUrl=https://cdn.example.com/catalog.json` |
| `avatarId` | ID of an existing digital twin to load immediately, skipping twin creation. | `?avatarId=dt_abc123` |

---

## 3. PostMessage Communication API

When the application runs inside an iframe, the host page and the application exchange messages via `window.postMessage`. This enables dynamic product updates and handling of try-on outcomes without reloading the iframe.

### Host → Application (Inbound Messages)

```javascript
const iframe = document.getElementById('wearfits-frame');

// Send initialization after WEARFITS_READY is received
iframe.contentWindow.postMessage({
  type: 'WEARFITS_INIT',
  payload: {
    products: [...],
    apiKey: 'your-api-key',
    baseUrl: 'https://api.wearfits.com'
  }
}, 'https://tryon.wearfits.com'); // Use specific origin in production
```

| Message Type | Payload | Description |
|--------------|---------|-------------|
| `WEARFITS_INIT` | `{ products, apiKey, baseUrl }` | Full initialization of the application state. Send this in response to `WEARFITS_READY`. |
| `WEARFITS_SET_PRODUCTS` | `[...products]` | Dynamically updates the garment list in the side panel without reinitializing the session. |

### Application → Host (Outbound Events)

```javascript
window.addEventListener('message', (event) => {
  // Always validate the origin in production
  if (event.origin !== 'https://tryon.wearfits.com') return;

  const { type, payload } = event.data;

  switch (type) {
    case 'WEARFITS_READY':
      // App has loaded — send WEARFITS_INIT now
      initializeApp();
      break;

    case 'WEARFITS_COMPLETE':
      console.log('Result image:', payload.resultImageUrl);
      console.log('Selected products:', payload.selectedProducts);
      break;

    case 'WEARFITS_ERROR':
      console.error('Error code:', payload.code, payload.message);
      break;

    case 'WEARFITS_CLOSE':
      // Hide the iframe or overlay
      document.getElementById('wearfits-modal').hidden = true;
      break;
  }
});
```

| Event Type | Payload | Description |
|------------|---------|-------------|
| `WEARFITS_READY` | `{ version: "1.0.0" }` | Emitted when the application has fully loaded and is ready to receive `WEARFITS_INIT`. |
| `WEARFITS_COMPLETE` | `{ resultImageUrl, selectedProducts, timestamp }` | Emitted on successful try-on. See [Fitting Result Object](#fitting-result-object). |
| `WEARFITS_ERROR` | `{ message, code }` | Emitted on any system or AI failure. |
| `WEARFITS_CLOSE` | `{}` | Emitted when the user requests to close the application. |

---

## 4. Product Data Schema

### Product Object

All integration methods accept products in the same format.

```json
{
  "id": "top-001",
  "name": "Classic White Blouse",
  "category": "top",
  "default": true,
  "images": [
    "https://cdn.example.com/products/blouse-model.jpg",
    "https://cdn.example.com/products/blouse-flat.jpg"
  ]
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | Yes | Unique product identifier. |
| `name` | string | Yes | Product display name shown in the selection panel. |
| `category` | string | Yes | Product category. See table below. |
| `images` | array | Yes | Array of 1–2 publicly accessible image URLs. |
| `default` | boolean | No | When `true`, the product is pre-selected when the application loads. |

### Product Categories

| Category | Value | Typical Items |
|----------|-------|---------------|
| Tops | `"top"` | T-shirts, blouses, shirts, sweaters, jackets |
| Bottoms | `"bottom"` | Pants, skirts, shorts |
| Full Body | `"fullBody"` | Dresses, jumpsuits, full outfits |
| Shoes | `"shoes"` | All footwear |

### Image Array Convention

The `images` array should contain 1–2 URLs:

- `images[0]` — Primary (lifestyle or model) image. Used in the rendered try-on result preview.
- `images[1]` — Secondary (packshot or flat-lay) image. Used in the product selection carousel.

If only one image is supplied, it is used for both purposes.

### Fitting Result Object

The `WEARFITS_COMPLETE` event and the `onComplete` SDK callback both receive the same result object:

```json
{
  "resultImageUrl": "https://api.wearfits.com/files/result_xyz.jpg",
  "selectedProducts": [
    { "id": "top-001", "name": "Classic White Blouse", "category": "top" },
    { "id": "bottom-001", "name": "Navy Dress Pants", "category": "bottom" }
  ],
  "timestamp": "2026-04-08T10:30:00Z"
}
```

---

## 5. Standalone Page (URL Parameters)

For the simplest possible integration — such as a "Try On" link that opens in a new tab — product data can be passed directly in the URL.

### Public size-fitting demo

Hosted mock API mode uses the `?mock=true` URL parameter only; it is separate from the public size-fitting demo. Open `https://tryon.wearfits.com/?sizeFitting=true&mockup=true` — both query parameters are required. It shows a sample person and simulated fit indicators; it does not create generations or make API requests, and it does not read or modify the shopper's saved avatar, pending jobs, or previous results. Use this demo to preview the UI only; it is not a size recommendation.

### Encoded JSON

```javascript
const products = [
  {
    id: 'top-001',
    name: 'Classic White Blouse',
    category: 'top',
    default: true,
    images: [
      'https://cdn.example.com/products/blouse-model.jpg',
      'https://cdn.example.com/products/blouse-flat.jpg'
    ]
  }
];

const url = new URL('https://tryon.wearfits.com/');
url.searchParams.set('products', JSON.stringify(products));

window.open(url.toString(), '_blank');
```

### Hosted JSON File

For larger catalogs where URL length is a concern, pass a URL to a hosted JSON file instead of inline data:

```
https://tryon.wearfits.com/?products=https://your-store.com/api/tryon-products.json
```

The application detects whether the `products` value is JSON or a URL automatically. The legacy `productsUrl` parameter is also accepted for backward compatibility.

Your server-hosted JSON should follow this structure:

```json
{
  "products": [
    {
      "id": "top-001",
      "name": "Classic White Blouse",
      "category": "top",
      "default": true,
      "images": ["https://cdn.example.com/products/blouse-model.jpg"]
    }
  ]
}
```

---
## 6. Shopify Integration

The recommended approach for Shopify is to render the WEARFITS experience as an iframe modal on the product detail page (PDP), initialized via a Shopify app proxy feed.

### Recommended Architecture

- **WEARFITS iframe source**: `https://tryon.wearfits.com`
- **Product feed endpoint**: `/apps/wearfits/products/{{ product.handle }}.json` (served via Shopify app proxy)
- **Category mapping**: Use a product metafield with namespace `wearfits` and key `category`; accepted values are `top`, `bottom`, `fullBody`, and `shoes`

This approach keeps the customer on the PDP throughout the try-on experience, maintains a clear path back to add-to-cart, and avoids exposing API credentials in the browser.

### App Proxy Setup (`shopify.app.toml`)

```toml
[access_scopes]
scopes = "write_app_proxy"

[app_proxy]
url = "/proxy/wearfits"
prefix = "apps"
subpath = "wearfits"
```

With this configuration:

- `https://{shop}.myshopify.com/apps/wearfits/products/{handle}.json` proxies to `https://{your-app}/proxy/wearfits/products/{handle}.json`

Run `shopify app dev` after updating `shopify.app.toml` to apply the proxy config to your development store. Production stores require `shopify app deploy`. Choose proxy `prefix` and `subpath` values carefully: changes to these after stores are installed only apply to new installs.

### Liquid Snippet — Try On Button (New-Tab Fallback)

Use this snippet on product pages to render a "Try On" button. The button is shown only for products that have a valid `wearfits.category` metafield, and opens the try-on experience in a new tab.

```liquid
{%- assign wearfits_category = product.metafields.wearfits.category.value | default: product.metafields.wearfits.category -%}

{%- if wearfits_category != blank -%}
  {%- capture wearfits_feed_url -%}
    {{ shop.url }}/apps/wearfits/products/{{ product.handle }}.json
  {%- endcapture -%}

  <button
    type="button"
    id="wearfits-tryon-button-{{ product.id }}"
    class="button button--secondary"
    data-wearfits-url="https://tryon.wearfits.com/?products={{ wearfits_feed_url | strip | url_encode }}"
  >
    Try On
  </button>

  <script>
    (() => {
      const button = document.getElementById('wearfits-tryon-button-{{ product.id }}');
      if (!button) return;
      button.addEventListener('click', () => {
        const url = button.getAttribute('data-wearfits-url');
        if (!url) return;
        window.open(url, '_blank', 'noopener,noreferrer');
      });
    })();
  </script>
{%- endif -%}
```

### PDP Modal with Iframe (Recommended)

For the full on-page experience, use an iframe modal initialized via PostMessage:

```html
<button type="button" id="wearfits-open-iframe">Try On</button>
<div id="wearfits-modal" hidden>
  <iframe
    id="wearfits-frame"
    src="https://tryon.wearfits.com"
    allow="camera"
    style="width: 100%; height: 100%; border: 0;"
  ></iframe>
</div>
```

```javascript
const button = document.getElementById('wearfits-open-iframe');
const modal  = document.getElementById('wearfits-modal');
const iframe = document.getElementById('wearfits-frame');
const feedUrl = `${window.Shopify.routes.root}apps/wearfits/products/{{ product.handle }}.json`;

button.addEventListener('click', () => {
  modal.hidden = false;
});

window.addEventListener('message', async (event) => {
  if (event.origin !== 'https://tryon.wearfits.com') return;

  if (event.data?.type === 'WEARFITS_READY') {
    const response = await fetch(feedUrl);
    const payload  = await response.json();
    iframe.contentWindow.postMessage(
      { type: 'WEARFITS_INIT', payload },
      'https://tryon.wearfits.com'
    );
  }

  if (event.data?.type === 'WEARFITS_COMPLETE') {
    console.log('Try-on result:', event.data.payload);
  }

  if (event.data?.type === 'WEARFITS_CLOSE') {
    modal.hidden = true;
  }
});
```

### Shopify Field Mapping

| Shopify Field | WEARFITS Field |
|---------------|----------------|
| `product.id` | `id` |
| `product.title` | `name` |
| `product.metafields.wearfits.category` | `category` |
| Featured image + first alternate image | `images` |
| Current PDP product | `default: true` |

### Feed Generation Rules

The app proxy endpoint should build its response using the following logic:

1. Load the current product by handle.
2. Read the `wearfits.category` metafield.
3. Include the current product with `default: true`.
4. Add compatible complementary products:
   - If the current category is `top`, add bottoms.
   - If the current category is `bottom`, add tops.
   - If the current category is `fullBody`, optionally add shoes.
   - If the current category is `shoes`, add a complete top + bottom or fullBody look.
5. Limit the response to 2–4 products total.
6. Return image URLs that are absolute, HTTPS, and publicly accessible.

---

## 7. Product Selection Rules

The try-on engine enforces the following garment combination rules:

- A **Top + Bottom** combination, or a single **FullBody** item, is required to generate a clothing try-on.
- **Shoes** are always optional and can be added to any outfit combination.
- Selecting a **FullBody** item automatically clears any Top or Bottom selections.
- Selecting a **Top** or **Bottom** automatically clears any FullBody selection.

These rules are enforced in the application UI; no additional handling is needed in the integration layer.

---

## 8. Best Practices

### Image Quality

- Use product images of at least **500 × 500 px**.
- Ensure consistent lighting and a white or neutral background.
- Include both a lifestyle (model) image and a packshot (flat-lay) where available — `images[0]` should be the lifestyle shot.
- Use HTTPS URLs for all images. Images must be publicly accessible; Data URLs are also supported.

### Security

- Always validate `postMessage` origins in production — use the specific iframe origin rather than `'*'`.
- Implement proper CORS headers on your image CDN to ensure images are accessible from the WEARFITS domain.

```javascript
// Production-safe postMessage handler
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://tryon.wearfits.com') return;
  // Process message...
});
```

For hosted SDK and iframe flows, WEARFITS handles background verification automatically. It prepares a verification check while the shopper selects garments and prepares the next check after a fitting submission is accepted, during generation or result viewing. A successful verification normally enables protected requests to reuse a secure pass for up to one hour, so a new challenge is not required for every request. If the pass expires, is unavailable, or is rejected after a network change, the app obtains a new verification automatically; an interactive challenge appears only when required. Hosts do not need to implement this flow.
### Performance

- Trigger a background warmup call to `https://api.wearfits.com/health/warmup` when the twin creation step first renders to reduce cold-start latency.
- Trigger a second warmup call to `https://api.wearfits.com/health/queue-warmup` when the garment selection step is displayed to pre-warm the fitting queue.
- Consider lazy-loading the try-on iframe so it does not block the initial page render.
- Digital twin IDs are cached automatically in browser cookies and localStorage; avoid manually clearing them between sessions.

### Camera Permissions

- Always include `allow="camera"` on the `<iframe>` element. Without it, browsers block camera access regardless of user permission grants.
- The application is fully responsive and optimized for mobile. Camera access is required for digital twin creation on all devices.

### CORS and Persistence

- Ensure your `baseUrl` proxy endpoint allows cross-origin requests from your host domain.
- In iframe mode, verify that browser settings permit third-party cookies if you want the digital twin resume-session feature to work across visits.
## 9. Direct API Job Status, Webhooks, and Size Fitting

### Polling asynchronous jobs

Direct API submissions such as `POST /api/v1/virtual-fitting` return a `jobId`. The response may also include `digitalTwinId`; this field is optional, so use `jobId` as the required handle for tracking and treat an absent `digitalTwinId` as valid.

Poll `GET /api/v1/jobs/{jobId}` until the job is `completed` or `failed`:

```javascript
async function waitForJob(jobId) {
  for (;;) {
    const response = await fetch(`/api/v1/jobs/${jobId}`, { cache: 'no-store' });
    if (!response.ok) throw new Error(`Job status failed: ${response.status}`);

    const job = await response.json();
    const percentage = job.progress?.percentage;

    if (percentage === undefined) {
      renderIndeterminate(job.progress?.stage || 'processing');
    } else {
      renderPercentage(percentage);
    }

    if (job.status === 'completed') return job;
    if (job.status === 'failed') throw new Error(job.error?.message || 'Try-on failed');

    await new Promise(resolve => setTimeout(resolve, 1500));
  }
}
```

Job status responses include `Cache-Control: no-store`. Respect this header and do not cache status responses by job ID; it controls HTTP caching but does not guarantee that a newly written status is immediately visible. A status change may take a short time to become visible, so tolerate an unchanged stage while polling rather than treating it as a failure.

The `progress` object and its `percentage` field may be absent. For `virtual-fitting` and `digital-twin` jobs, progress reports lifecycle stages rather than measured provider progress: `percentage` is absent while processing and is `100` only when the job is completed. Display an indeterminate spinner whenever the percentage is absent, and do not show an estimated countdown. Other asynchronous job types may continue to report percentage progress.

### Webhook delivery

If you configure a webhook for an asynchronous job, delivery is **at least once**, not exactly once. Temporary delivery failures can cause the same terminal notification to be sent again. Make the webhook handler idempotent: record the job ID and completion state before applying side effects, and ignore a notification that has already been processed. A redelivery repeats notification delivery only; it does not start another generation.

### Size-fitting request and response

`POST /api/v1/size-fitting` is a synchronous, non-billable estimate for an eligible digital twin created from body measurements or clothing size. Photo-only and direct twins return `available: false` instead of a size recommendation.

Send a `digitalTwinId` and one or more products with body-based size charts:

```json
{
  "digitalTwinId": "dt_abc123",
  "products": [
    {
      "id": "shirt-001",
      "category": "top",
      "sizeChart": {
        "version": 1,
        "basis": "body",
        "unit": "cm",
        "sizes": [
          {
            "label": "S",
            "measurements": {
              "chest": { "min": 84, "max": 92 },
              "waist": { "min": 68, "max": 76 }
            }
          },
          {
            "label": "M",
            "measurements": {
              "chest": { "min": 92, "max": 100 },
              "waist": { "min": 76, "max": 84 }
            }
          }
        ]
      }
    }
  ]
}
```

The chart must use `version: 1`, `basis: "body"`, and either `cm` or `in` units. Supported categories are `top`, `bottom`, and `fullBody`. Use body dimensions rather than garment dimensions: tops use chest or waist, bottoms use waist, hip, or inseam, and full-body items use chest, waist, hip, or height. At least one relevant dimension must be present in every size row.

Products with no relevant dimension available in both the private profile and every size row are omitted. If no product remains comparable, the endpoint returns `available: false`; this is a valid estimate outcome, not an HTTP or processing error. An `available: true` response contains estimated recommendations and per-dimension evaluations:

```json
{
  "success": true,
  "available": true,
  "confidence": "estimated",
  "recommendations": [
    {
      "productId": "shirt-001",
      "category": "top",
      "recommendedSize": "M",
      "alternatives": ["S"],
      "dimensions": {
        "chest": { "status": "fit", "score": 0.25 },
        "waist": { "status": "fit", "score": -0.1 }
      }
    }
  ]
}
```

Dimension statuses are `fit`, `loose`, or `tight`. Scores are bounded; positive values indicate a looser or longer result, while negative values indicate a tighter or shorter result. Handle `available: false` as a valid response—including for an ineligible twin or when no submitted product has a comparable dimension—not as an HTTP or processing error. When `available: true`, use `confidence: "estimated"` to present recommendations as estimates rather than guaranteed size advice.
