---
title: Clothing AI Try-On — API Reference
product: genai-tryon
last_updated: '2026-09-21'
description: "This document covers the digital-twin and virtual-fitting endpoints of the WEARFITS GenAI API. For 3D shoe generation (/api/v1/shoe-3d) refer to the..."
---

# Clothing AI Try-On — API Reference

This document covers the **digital-twin** and **virtual-fitting** endpoints of the WEARFITS GenAI API. For 3D shoe generation (`/api/v1/shoe-3d`) refer to the separate Shoe-3D API Reference.

---

## Base Information

| Property | Value |
|---|---|
| Production base URL | `https://api.wearfits.com` |
| OpenAPI spec | [api.wearfits.com/doc](https://api.wearfits.com/doc) |
| Interactive docs (Swagger UI) | [api.wearfits.com/reference](https://api.wearfits.com/reference) |
| Authentication header | `X-API-Key` |

All `/api/*` routes require the `X-API-Key` header on every request.

```http
X-API-Key: your_api_key_here
```

---

### Authentication error codes

Authentication failures include a machine-readable `error.code` field. When an account-scoped credential can no longer be verified, the API returns HTTP `401` with `ACCOUNT_TOKEN_INVALID`. Discard that credential, obtain a new one, and retry the request.

Do not branch on the human-readable error message. Other API-key authentication failures continue to use `HTTP_ERROR` and are not fixed by replacing an account-scoped credential.
## Bot Protection for Clothing Requests

Protected browser-facing clothing requests accept either a single-use `X-Turnstile-Token` or a valid signed verification pass. A successful verification pass is valid for **one hour** from verification, is bound to the originating client network, and is not extended by later use. Preserve cookies between requests when relying on the pass.

Successful protected responses expose the remaining pass lifetime in the `X-Wearfits-Verification-TTL` response header, in seconds. If the pass expires or the client network changes, perform a new verification. The pass supplements `X-API-Key` authentication; it does not replace it.
## Image Input Formats

Every image field in the API accepts one of two formats:

**HTTP/HTTPS URL** — a publicly accessible URL pointing to a hosted image.

**Base64 Data URL** — an inline base64-encoded image embedded directly in the JSON body.

```
data:image/<format>;base64,<base64-encoded-data>
```

Supported MIME types: `image/png`, `image/jpeg`, `image/jpg`, `image/gif`, `image/webp`, `image/svg+xml`.

---

## Available Poses

The `poseId` field accepts the following values across all endpoints that support posing:

| poseId | Description |
|---|---|
| `standing_arms_down` | Neutral standing, arms at sides |
| `man_pose` | Male standing pose |
| `girl_pose` | Female standing pose (default) |
| `shoe_girl_pose` | Female pose optimized for footwear display |

---

## Body Measurement Ranges
When supplying `bodyMeasurements`, values must fall within these validation bounds. At least **two** measurements are required.

| Field | Unit | Min | Max |
|---|---|---|---|
| `height` | cm | 140 | 210 |
| `chest` | cm | 70 | 160 |
| `waist` | cm | 55 | 170 |
| `hip` | cm | 70 | 170 |
| `inseam` | cm | 60 | 100 |

These are validation bounds, not a guarantee that every value has an exact reference match. Values within the bounds may be matched to the nearest available body shape, so the generated result can represent an extrapolative match.
## Core Endpoints

### POST /api/v1/digital-twin

Generate a photorealistic digital twin (avatar) for use in virtual try-on sessions. This is an asynchronous operation — the endpoint returns a `jobId` which you poll via `GET /api/v1/jobs/{jobId}` to retrieve the `digitalTwinId` on completion.

**Input Modes**

| Mode | Required fields | How it works |
|---|---|---|
| **Photo Mode** | `faceImage` + `bodyPhotoUrl` | SAM-3D extracts the body mesh automatically from the full-body photo |
| **Size Mode (measurements)** | `faceImage` + `bodyMeasurements` | Nearest-neighbour body mask lookup from the BodyM dataset |
| **Size Mode (clothing size)** | `faceImage` + `clothingSize` | Simplified size input; converted internally to measurements |

**Request Body**

| Field | Type | Required | Description |
|---|---|---|---|
| `faceImage` | string | Yes | URL or Base64 Data URL of the user's face |
| `bodyPhotoUrl` | string | Photo Mode | URL or Base64 Data URL of full-body photo |
| `bodyMeasurements` | object | Size Mode | Body dimensions object — see ranges above |
| `bodyMeasurements.height` | number | — | Height in cm |
| `bodyMeasurements.chest` | number | — | Chest circumference in cm |
| `bodyMeasurements.waist` | number | — | Waist circumference in cm |
| `bodyMeasurements.hip` | number | — | Hip circumference in cm |
| `bodyMeasurements.inseam` | number | — | Inseam length in cm |
| `clothingSize` | object | Size Mode | Simplified size input |
| `clothingSize.height` | number | — | Height in cm |
| `clothingSize.size` | string | — | Standard clothing size (e.g. `"M"`, `"L"`, `"XL"`) |
| `gender` | string | Recommended | `"male"` or `"female"` |
| `poseId` | string | No | Target pose — see Available Poses above |

**Response**

```json
{
  "jobId": "job_abc123",
  "estimatedTime": 30
}
```

Once the job reaches `completed` status, `GET /api/v1/jobs/{jobId}` returns:

```json
{
  "status": "completed",
  "result": {
    "digitalTwinId": "twin_xyz789"
  }
}
```

**Example — Photo Mode**

```bash
curl -X POST https://api.wearfits.com/api/v1/digital-twin \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "faceImage": "https://example.com/user-face.jpg",
    "bodyPhotoUrl": "https://example.com/user-body.jpg",
    "gender": "female",
    "poseId": "girl_pose"
  }'
```

**Example — Size Mode (measurements)**

```bash
curl -X POST https://api.wearfits.com/api/v1/digital-twin \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "faceImage": "https://example.com/user-face.jpg",
    "bodyMeasurements": {
      "height": 170,
      "chest": 88,
      "waist": 70,
      "hip": 96
    },
    "gender": "female"
  }'
```

**Example — Size Mode (clothing size)**

```bash
curl -X POST https://api.wearfits.com/api/v1/digital-twin \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "faceImage": "https://example.com/user-face.jpg",
    "clothingSize": {
      "height": 175,
      "size": "M"
    },
    "gender": "male"
  }'
```

---

### GET /api/v1/digital-twin/{id}

Verify that a `digitalTwinId` still exists in cache. Useful when persisting a twin ID in client storage (cookies, localStorage) to confirm it is still available before initiating a try-on session without regenerating the avatar.

**Path Parameters**

| Parameter | Type | Description |
|---|---|---|
| `id` | string | The `digitalTwinId` to validate |

**Response — valid twin**

```json
{
  "valid": true
}
```

**Response — expired or unknown twin**

HTTP `404` or `{ "valid": false }`.

**Caching policy:** Digital twins are cached for **30 days** from generation. After expiry, a new twin must be generated via `POST /api/v1/digital-twin`.

**Example**

```bash
curl https://api.wearfits.com/api/v1/digital-twin/twin_xyz789 \
  -H "X-API-Key: your_api_key_here"
```

---

### POST /api/v1/virtual-fitting

The primary virtual try-on pipeline. Applies one or more garments onto a digital twin and returns a photorealistic composite image. The request may complete immediately when a reusable result is available. Otherwise, it is asynchronous and returns a `jobId` with `status: "queued"` for polling.

You can either supply a pre-generated `digitalTwinId` (recommended for returning users) or pass inline body/face data to generate a twin on-the-fly within the same request.

**Request Body**

*Identity — choose one approach:*

| Field | Type | Description |
|---|---|---|
| `digitalTwinId` | string | ID of a previously generated digital twin (preferred) |
| `faceImage` | string | URL or Base64; used with inline body data |
| `photoUrl` | string | Full-body photo URL or Base64 (Photo Mode inline) |
| `bodyMeasurements` | object | Body dimensions (Size Mode inline) |
| `clothingSize` | object | Simplified size (Size Mode inline) |

*Garments — at least one garment field is required:*

| Field | Type | Description |
|---|---|---|
| `topGarment` | string \| [string, string] | Single image URL/Base64, or a pair `[front, back]` |
| `bottomGarment` | string \| [string, string] | Bottom garment image(s) |
| `fullBodyGarment` | string \| [string, string] | Full-body garment (e.g. dress, jumpsuit) |
| `shoes` | string \| [string, string] | Shoe image(s) |

*Presentation options:*

| Field | Type | Description |
|---|---|---|
| `gender` | string | `"male"` or `"female"` |
| `poseId` | string | Target pose — see Available Poses above |

**Response — queued**

```json
{
  "success": true,
  "jobId": "job_def456",
  "status": "queued",
  "estimatedProcessingTime": 20
}
```

A completed fitting can also be returned directly from the submission request. When inline body data creates or reuses a digital twin, the response may include `digitalTwinId`:

```json
{
  "success": true,
  "digitalTwinId": "twin_xyz789",
  "jobId": "job_def456",
  "status": "completed",
  "results": [
    {
      "url": "https://api.wearfits.com/files/result.png"
    }
  ]
}
```

Use the returned `results` directly when `status` is `completed`; do not make an additional poll. Only poll `GET /api/v1/jobs/{jobId}` when the submission response has `status: "queued"`. A completed response may also include `digitalTwinId` when inline body data produced or reused a twin.

**Example — fitting with pre-generated twin**

```bash
curl -X POST https://api.wearfits.com/api/v1/virtual-fitting \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "digitalTwinId": "twin_xyz789",
    "topGarment": "https://example.com/shirt-front.jpg",
    "bottomGarment": "https://example.com/trousers.jpg",
    "gender": "male",
    "poseId": "man_pose"
  }'
```

**Example — fitting with front and back garment images**

```bash
curl -X POST https://api.wearfits.com/api/v1/virtual-fitting \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "digitalTwinId": "twin_xyz789",
    "topGarment": [
      "https://example.com/jacket-front.jpg",
      "https://example.com/jacket-back.jpg"
    ],
    "gender": "female",
    "poseId": "girl_pose"
  }'
```

**Example — inline body data (no pre-generated twin)**

```bash
curl -X POST https://api.wearfits.com/api/v1/virtual-fitting \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "faceImage": "https://example.com/user-face.jpg",
    "bodyMeasurements": {
      "height": 168,
      "waist": 68,
      "hip": 94
    },
    "fullBodyGarment": "https://example.com/dress.jpg",
    "gender": "female"
  }'
```

---
### POST /api/v1/size-fitting
Estimate clothing sizes from the private body profile associated with a digital twin. This is a synchronous, non-billable calculation.

Only digital twins created from `bodyMeasurements` or `clothingSize` are eligible. Photo-mode and other direct-photo twins return `available: false`.

**Request Body**

| Field | Type | Required | Description |
|---|---|---|---|
| `digitalTwinId` | string | Yes | ID of an eligible digital twin |
| `products` | array | Yes | Products to evaluate against their body-measurement size charts |
| `products[].id` | string | Yes | Product identifier |
| `products[].category` | string | Yes | `top`, `bottom`, or `fullBody` |
| `products[].sizeChart` | object | Yes | Versioned body-measurement chart |
| `sizeChart.version` | number | Yes | Must be `1` |
| `sizeChart.basis` | string | Yes | Must be `"body"`; provide body ranges, not garment dimensions |
| `sizeChart.unit` | string | Yes | `"cm"` or `"in"` |
| `sizeChart.sizes` | array | Yes | One or more size rows, with unique labels |
| `sizes[].label` | string | Yes | Display label such as `"S"`, `"M"`, or `"L"` |
| `sizes[].measurements` | object | Yes | Optional ranges for `height`, `chest`, `waist`, `hip`, and `inseam` |
| `measurements.<dimension>.min` | number | Yes when dimension is supplied | Lower bound of the body range |
| `measurements.<dimension>.max` | number | Yes when dimension is supplied | Upper bound; must be greater than or equal to `min` |

Every size row must share at least one relevant dimension for the product category. The relevant dimensions are `chest` and `waist` for `top`, `waist`, `hip`, and `inseam` for `bottom`, and `chest`, `waist`, `hip`, and `height` for `fullBody`. Ranges supplied in inches are converted before evaluation.

**Example**

```bash
curl -X POST https://api.wearfits.com/api/v1/size-fitting \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -d '{
    "digitalTwinId": "twin_xyz789",
    "products": [{
      "id": "shirt-1",
      "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 HTTP `200` response reports whether sizing is `available` and, when it is, returns estimated recommendations with per-dimension `fit`, `loose`, or `tight` statuses and bounded scores. A profile that cannot be used for sizing returns `available: false`. Treat missing optional response fields as unavailable rather than as errors.
## Job Management Endpoints

### GET /api/v1/jobs/{jobId}

Poll the status and retrieve results of any asynchronous job (digital twin generation, virtual fitting, etc.). For `digital-twin` and `virtual-fitting` jobs, `progress.stage` reports the lifecycle stage while the job is running; `progress.percentage` is omitted until completion, when it is `100`. Render indeterminate progress while the percentage is absent rather than treating the stage as an estimated countdown.

Job-status responses include `Cache-Control: no-store`, so clients should not reuse cached polling responses. This prevents HTTP cache reuse but does not guarantee immediate visibility if status storage is still propagating. Client-side polling should also disable its request cache, for example with `cache: "no-store"` in `fetch`.

For `digital-twin` and `virtual-fitting` jobs, poll at a one-second start-to-start cadence without overlapping requests, for up to **600 attempts** (approximately ten minutes). If a status request fails, retry after two seconds. Other tools can continue using a two-second response-to-start interval. Stop polling as soon as the job reaches a terminal status.

**Path Parameters**

| Parameter | Type | Description |
|---|---|---|
| `jobId` | string | Job ID returned by a submission endpoint |

**Job Status Values**

| Status | Meaning |
|---|---|
| `queued` | Job is waiting in the processing queue |
| `validating` | Input data is being validated |
| `processing` | AI models are generating the result |
| `uploading` | Output is being uploaded to storage |
| `completed` | Job finished successfully; `result` is populated |
| `failed` | Job failed; `error` field describes the cause |

**Response — in-progress**

For virtual-fitting and digital-twin jobs, the percentage is absent while processing:

```json
{
  "jobId": "job_abc123",
  "status": "processing",
  "progress": {
    "stage": "processing"
  }
}
```

**Response — completed virtual fitting**

```json
{
  "jobId": "job_def456",
  "status": "completed",
  "progress": 100,
  "result": {
    "imageUrl": "https://cdn.wearfits.com/results/job_def456/output.jpg"
  }
}
```

**Response — completed digital twin**

```json
{
  "jobId": "job_abc123",
  "status": "completed",
  "progress": 100,
  "result": {
    "digitalTwinId": "twin_xyz789"
  }
}
```

**Example — polling loop (bash)**

```bash
JOB_ID="job_def456"

while true; do
  # Keep polling one second start-to-start without overlapping requests.
  SECONDS=0
  RESPONSE=$(curl -s "https://api.wearfits.com/api/v1/jobs/$JOB_ID" \
    -H "X-API-Key: your_api_key_here")

  STATUS=$(echo "$RESPONSE" | jq -r '.status')
  echo "Status: $STATUS"

  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
    echo "$RESPONSE" | jq .
    break
  fi

  WAIT=$((1 - SECONDS))
  if [ "$WAIT" -gt 0 ]; then
    sleep "$WAIT"
  fi
done
```

**Example — polling loop (JavaScript)**

```javascript
async function pollJob(jobId, apiKey, intervalMs = 1000) {
  const url = `https://api.wearfits.com/api/v1/jobs/${jobId}`;

  while (true) {
    const pollStartedAt = performance.now();
    const res = await fetch(url, {
      cache: 'no-store',
      headers: { 'X-API-Key': apiKey },
    });
    const job = await res.json();

    if (job.status === 'completed') {
      return job.result;
    }
    if (job.status === 'failed') {
      throw new Error(`Job failed: ${job.error}`);
    }

    // Start the next request one second after this request started.
    const waitMs = Math.max(0, intervalMs - (performance.now() - pollStartedAt));
    await new Promise((resolve) => setTimeout(resolve, waitMs));
  }
}
```

---
### DELETE /api/v1/jobs/{jobId}

Cancel a running or queued job immediately.

**Path Parameters**

| Parameter | Type | Description |
|---|---|---|
| `jobId` | string | ID of the job to cancel |

**Response**

HTTP `200` on successful cancellation.

**Example**

```bash
curl -X DELETE https://api.wearfits.com/api/v1/jobs/job_def456 \
  -H "X-API-Key: your_api_key_here"
```

---
## Secondary Endpoints

The following endpoints provide standalone or utility functionality. They follow the same authentication and job-polling patterns as the core endpoints.

| Method | Path | Description |
|---|---|---|
| POST | `/api/v1/tryon/clothing` | Standalone clothing try-on (without a full digital twin pipeline) |
| POST | `/api/v1/tryon/shoes` | Standalone shoe try-on |
| POST | `/api/v1/generate` | Generate images from a text prompt |
| POST | `/api/v1/pose-transfer` | Transfer the pose of one person onto another |

---

## Recommended Integration Flow

The following pattern covers the typical e-commerce use case: a user visits a product page, generates a personal avatar once, then tries on multiple garments using that avatar.

```
1. User uploads face photo (+ body photo OR measurements OR clothing size)
        │
        ▼
2. POST /api/v1/digital-twin
        │
        ▼
3. Poll GET /api/v1/jobs/{jobId}  ──► status: completed
        │
        ▼  result.digitalTwinId
4. Store digitalTwinId client-side (cookie / localStorage)
        │
   On subsequent visits:
5. GET /api/v1/digital-twin/{id}  ──► valid: true  (skip step 1–4)
        │
        ▼
6. POST /api/v1/virtual-fitting   { digitalTwinId, topGarment, … }
        │
        ├── status: completed ──► use returned results
        │
        └── status: queued
                │
                ▼
7. Poll GET /api/v1/jobs/{jobId}  ──► status: completed
                │
                ▼  result.imageUrl / results[].url
8. Display try-on image
```

---
## Webhook Signatures

If you configure a webhook URL to receive job completion notifications, each request is signed so you can verify its authenticity.

**Signing algorithm:** HMAC-SHA256  
**Secret derivation:** `hex( SHA-256( your_api_key ) )`

**Verification steps:**

1. Derive the signing secret:
   ```python
   import hashlib
   signing_secret = hashlib.sha256(api_key.encode()).hexdigest()
   ```

2. Sort the JSON body keys alphabetically and serialize to a canonical JSON string.

3. Compute the HMAC-SHA256 of the canonical JSON string using the signing secret.

4. Compare your computed signature against the value in the `X-Webhook-Signature` request header.

**Python verification example:**

```python
import hashlib
import hmac
import json

def verify_webhook(payload: bytes, received_signature: str, api_key: str) -> bool:
    # Derive signing secret from API key
    signing_secret = hashlib.sha256(api_key.encode()).hexdigest()

    # Canonical form: parse and re-serialize with sorted keys
    body = json.loads(payload)
    canonical = json.dumps(body, sort_keys=True, separators=(',', ':'))

    # Compute expected signature
    expected = hmac.new(
        signing_secret.encode(),
        canonical.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, received_signature)
```

**Node.js verification example:**

```javascript
const crypto = require('crypto');

function verifyWebhook(rawBody, receivedSignature, apiKey) {
  // Derive signing secret from API key
  const signingSecret = crypto
    .createHash('sha256')
    .update(apiKey)
    .digest('hex');

  // Canonical form: parse and re-serialize with sorted keys
  const body = JSON.parse(rawBody);
  const sorted = JSON.stringify(body, Object.keys(body).sort());

  // Compute expected signature
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(sorted)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
}
```

---

## Webhook Delivery and Retries

Webhook delivery is **at least once**, not exactly once. Duplicate notifications are possible, so make the webhook handler idempotent and deduplicate using the job ID or another stable event identifier.

WEARFITS retries webhook enqueue and delivery failures. If a completed job is redelivered, the retry delivers the notification and performs idempotent completion bookkeeping; it does not regenerate the fitting or digital twin.

For a cache-hit fitting, WEARFITS enqueues the webhook before returning success. If the webhook cannot be enqueued, the request returns HTTP `500` and neither usage counter is charged. Retry the request safely; a successful retry reuses the cached result.
## Rate Limits

Rate limits are enforced per API key.

| Endpoint category | Limit |
|---|---|
| Job submission | 50 requests / minute |
| Job status polling | 500 requests / minute |
| File downloads | 300 requests / minute |
| Health check | 5 000 requests / minute |

Limits are **10× higher** when using `localhost` or the `DEV_API_KEY` (development environment only).

If you exceed a limit, the API returns HTTP `429 Too Many Requests`. Implement exponential back-off in your polling loop to stay within the job-status limit during high-concurrency scenarios.
