Skip to content
View Markdown

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.


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

<!-- 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

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.
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

<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)

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)

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.
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.

{
  "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:

{
  "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

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:

{
  "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.

  • 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)

[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.

{%- 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 -%}

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

<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>
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:
  5. If the current category is top, add bottoms.
  6. If the current category is bottom, add tops.
  7. If the current category is fullBody, optionally add shoes.
  8. If the current category is shoes, add a complete top + bottom or fullBody look.
  9. Limit the response to 2–4 products total.
  10. 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.
// 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:

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:

{
  "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:

{
  "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.