---
title: 2D to 3D Generator — API Reference
product: shoe3d-generator
last_updated: '2026-10-26'
description: "Base URL: https://api.wearfits.com"
---

# 2D to 3D Generator — API Reference

Base URL: `https://api.wearfits.com`

Interactive documentation:
- **OpenAPI Spec:** [api.wearfits.com/doc](https://api.wearfits.com/doc)
- **Swagger UI:** [api.wearfits.com/reference](https://api.wearfits.com/reference)
- **Shoe AR Integration Guide:** [tryon.wearfits.com/docs/integration-shoes](https://tryon.wearfits.com/docs/integration-shoes)

---

## Authentication

All `/api/*` routes require the `X-API-Key` header. API keys are created and managed at [dash.wearfits.com](https://dash.wearfits.com).

```bash
X-API-Key: your_api_key
```

Requests without a valid key return HTTP **401**.

Browser-based WEARFITS flows also protect shoe generation, file upload, and job requests with Turnstile verification. The hosted application manages this automatically: successful verification sets a signed, HttpOnly pass cookie bound to the browser's network. The pass is valid for a fixed one hour from verification (use does not extend it) and can authorize later protected requests from that same network without another Turnstile check. A request with a Turnstile token uses that token and can mint a fresh pass; if a pass expires or is rejected, the application retries with fresh verification. Keep cookies enabled for these flows. Protected responses expose the remaining pass lifetime in `X-Wearfits-Verification-TTL`; a browser-protected request with neither a valid token nor pass returns HTTP **403**. The verification pass does not replace `X-API-Key` for direct API integrations.

---
## Image Input Formats

All `images` fields across the API accept two formats:

1. **URLs (HTTP/HTTPS)** — standard web URLs pointing to publicly accessible hosted images.
2. **Base64 Data URLs** — inline base64-encoded images embedded directly in the request body.
   - Format: `data:image/<format>;base64,<data>`
   - Supported image formats: `png`, `jpeg`, `jpg`, `gif`, `webp`, `svg+xml`

---

## Endpoints

### POST /api/v1/shoe-3d

Generate a high-quality 3D shoe model from one or more 2D product photos. If a model-specific failure occurs, WEARFITS automatically retries with an alternate supported model, when available, for up to four attempts total before marking the job failed. Clients generally do not need to resubmit for an individual model outage; poll the job and handle a final `failed` status if all attempts are unsuccessful. The request is accepted immediately and processed asynchronously. Use [`GET /api/v1/jobs/{jobId}`](#get-apiv1jobsjobid) to poll for results.

**Returns:** `202 Accepted` with a `jobId`.

#### Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `images` | `string[]` | Yes | List of shoe photo URLs or base64 data URLs. Provide multiple angles for best results. |
| `glbInput` | `string` | No | URL to an existing GLB file to use as the base for transformation or texture enhancement instead of generating from scratch. For large GLBs, upload the file with `POST /api/v1/files/upload` and pass the returned `url` here. |
| `options` | `object` | No | Processing options (see below). |
| `options.models` | `string[]` | No | Optional list of supported model identifiers for generation. The service uses its default selection when omitted. |
| `options.modelName` | `string` | No | Optional 1–50 character display name for the model uploaded to WEARFITS. |
| `options.uploadToWearFits` | `boolean` | No | After generation, automatically upload the model to WEARFITS AR. |
| `options.sessionId` | `string` | No | Optional WEARFITS session identifier for ownership. When supplied, it takes precedence over the account associated with the API credential and is the only ownership claim used for the upload. |
| `options.enhanceTexture` | `boolean` | No | Run an AI texture enhancement pass after 3D generation. Gap filling and baked ambient occlusion are enabled by default during this pass unless you opt out with the options below. |
| `options.gapFillReproject` | `boolean` | No | Re-project texture gaps during texture enhancement. Defaults to `true` when `enhanceTexture` is enabled; set to `false` to opt out. |
| `options.aoBake` | `string` | No | Bake ambient occlusion into the base-color texture during texture enhancement. Defaults to `"baked"` when `enhanceTexture` is enabled. Accepted values are `"baked"` and `"off"`; set to `"off"` to opt out. |
| `options.aoStrength` | `number` | No | Baked ambient-occlusion strength from `0` to `1`. Defaults to `0.7` when AO baking is enabled. |
| `options.correctImage` | `boolean` | No | Apply professional packshot correction to the input photos before 3D generation. |
| `options.productType` | `string` | No | Hint for the generation model. One of: `shoe`, `other`, or `auto` (default). |
| `options.longestSideCm` | `number` | No | For bags and other non-shoe products, the real length of the longest horizontal side (width or depth, never the height), from 5 to 150 cm with 0.1 cm precision. Applies to `productType: "other"` and to `"auto"` when it resolves to a non-shoe; ignored for shoes. |
| `options.refineLogo` | `boolean` | No | Run a second AI pass focused specifically on fixing logos and brand text on the generated model. Non-fatal: if the logo pass fails, the pipeline continues with first-pass results. |
| `options.genaiQuality` | `string` | No | Controls texture enhancement quality and render count. `"default"` uses 4 views (3-minute timeout). `"high"` uses 6 views (5-minute timeout). Defaults to `"high"` when `enhanceTexture` is enabled. |

When `enhanceTexture` is enabled, gap filling and baked ambient occlusion run by default. Set `gapFillReproject: false` and/or `aoBake: "off"` to disable either step. These options apply only during texture enhancement; explicitly enabling gap filling or AO baking with `enhanceTexture: false` returns HTTP **400**.

For bags and other non-shoe products, `options.longestSideCm` sets the real length of the longest horizontal side (width or depth, never the height). Values must be between 5 and 150 cm and are stored with 0.1 cm precision. Out-of-range values return HTTP **400** before a job is created. If the value is omitted, the try-on keeps the model's own scale when it is plausible for a bag (between 10 and 100 cm across) and shows it at 40 cm otherwise. For `productType: "other"`, submissions are never deduplicated and never reuse a WEARFITS link; each creates its own job and model.

#### Who the uploaded model belongs to

When `options.uploadToWearFits` is `true`, the generated model is assigned to exactly one WEARFITS owner:

1. **The account associated with the API credential (automatic).** If the credential resolves to a WEARFITS account, that account's library receives the model. No additional field is required.
2. **`options.sessionId` (explicit).** Use this when your frontend already has a WEARFITS user session and the model should be linked to that session.

`options.sessionId` takes precedence. When it is present, it is the only ownership claim used for the upload; the account associated with the credential is not used. Do not provide conflicting ownership claims.

If neither an account association nor a session is available, the model is placed in the integration's default WEARFITS library rather than linked to an individual session.

#### Example request

```bash
curl -X POST https://api.wearfits.com/api/v1/shoe-3d \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      "https://example.com/shoe-side.jpg",
      "https://example.com/shoe-front.jpg",
      "https://example.com/shoe-back.jpg"
    ],
    "options": {
      "uploadToWearFits": true,
      "enhanceTexture": true,
      "correctImage": true,
      "productType": "shoe",
      "refineLogo": false,
      "genaiQuality": "high"
    }
  }'
```

#### Example request with base64 image

```bash
curl -X POST https://api.wearfits.com/api/v1/shoe-3d \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      "data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."
    ],
    "options": {
      "productType": "shoe"
    }
  }'
```

#### Example request with existing GLB

```bash
curl -X POST https://api.wearfits.com/api/v1/shoe-3d \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      "https://example.com/shoe-packshot.jpg"
    ],
    "glbInput": "https://example.com/base-model.glb",
    "options": {
      "enhanceTexture": true,
      "uploadToWearFits": true
    }
  }'
```

#### WEARFITS upload limit and related errors

When `options.uploadToWearFits` is enabled, the final GLB uploaded to WEARFITS must be no larger than **20 MB**. The service attempts to compress oversized output before uploading it. If the result still exceeds the limit, the job fails instead of uploading the model. Use a smaller or simpler source model, or disable the WEARFITS upload.

Upload failures use these codes:

| Code | Meaning | Recommended action |
|------|---------|-------------------|
| `attribution_unavailable` | WEARFITS temporarily could not resolve the attributed account. | Retry the request or job after the temporary service issue clears. |
| `invalid_user_id` | The supplied session or account owner identifier is not valid. | Correct the ownership value and submit a new job. |
| `attribution_conflict` | The upload contains conflicting ownership claims. | Use one ownership method; `options.sessionId` takes precedence when supplied. |
| `file_too_large` | The final GLB remains above the **20 MB** WEARFITS upload ceiling after compression. | Use a smaller or simpler model, or omit `uploadToWearFits`. |

#### Response — 202 Accepted

```json
{
  "jobId": "job_01HX3Z7K2MFVBN9Q4RPTDY6WCE"
}
```

---
### POST /api/v1/files/upload

Upload a large binary file (such as a source `.glb` model) before triggering the `shoe-3d` pipeline. Use this endpoint when your GLB exceeds the inline payload size limit for the `shoe-3d` request body.

The endpoint returns a temporary hosted URL. Pass the returned `url` unchanged as `glbInput` in a subsequent `POST /api/v1/shoe-3d` call.

#### Request

**Content-Type:** `multipart/form-data`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | file | Yes | The binary file to upload (e.g., `.glb`). |

#### Example request

```bash
curl -X POST https://api.wearfits.com/api/v1/files/upload \
  -H "X-API-Key: your_api_key" \
  -F "file=@base-model.glb"
```

#### Response — 200 OK

```json
{
  "url": "https://storage.example.com/uploads/base-model.glb"
}
```

Use the returned `url` as the `glbInput` value when calling `POST /api/v1/shoe-3d`.

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

Poll the status and retrieve the final results of an asynchronous job submitted via `POST /api/v1/shoe-3d` or any other job-producing endpoint. Responses include `Cache-Control: no-store`; the hosted application proxy preserves this header, so do not cache job-status responses. Proxied responses also include `X-Content-Type-Options: nosniff`. These headers prevent browser/intermediary cache reuse and MIME sniffing, although a newly written status may still take a short time to become visible.

Some non-shoe job types expose lifecycle stages without measured provider progress. For `virtual-fitting` and `digital-twin`, `progress.percentage` is absent until completion, when it is `100`; render an indeterminate spinner while it is absent.

#### Path parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `jobId` | `string` | Job identifier returned by the submission endpoint. |

#### Example request

```bash
curl https://api.wearfits.com/api/v1/jobs/job_01HX3Z7K2MFVBN9Q4RPTDY6WCE \
  -H "X-API-Key: your_api_key"
```

#### Response — 200 OK (in progress)

```json
{
  "jobId": "job_01HX3Z7K2MFVBN9Q4RPTDY6WCE",
  "status": "processing",
  "progress": 42
}
```

#### Response — 200 OK (completed)

```json
{
  "jobId": "job_01HX3Z7K2MFVBN9Q4RPTDY6WCE",
  "status": "completed",
  "progress": 100,
  "result": {
    "glbUrl": "https://assets.wearfits.com/models/shoe.glb",
    "viewerUrl": "https://app.wearfits.com/viewer?object=abc123",
    "previewGrid": "https://assets.wearfits.com/previews/shoe-grid.png"
  }
}
```

Generated GLB and preview assets use `assets.wearfits.com`. Treat returned asset URLs as opaque and use the returned `glbUrl` unchanged when passing the model to downstream integrations, including Fittingbox; `assets.wearfits.com` is an accepted WEARFITS asset host.

#### Response — 200 OK (failed)

```json
{
  "jobId": "job_01HX3Z7K2MFVBN9Q4RPTDY6WCE",
  "status": "failed",
  "error": "Generation failed: no valid shoe silhouette detected in input images."
}
```

For a shoe-3d job that ends with `error.code: "CACHE_STATE_INVALIDATED"`, submit a new request. The job is terminal (`error.retryable: false`) and has no usable result.

#### Job status values

| Status | Description |
|--------|-------------|
| `queued` | Job has been accepted and is waiting for a processing slot. |
| `validating` | Input images and parameters are being validated. |
| `processing` | 3D generation and optional AI passes are running. |
| `uploading` | Generated model is being uploaded to storage and, if requested, to the AR platform. |
| `completed` | Processing is complete. The `result` object contains output URLs. |
| `failed` | An unrecoverable error occurred. See the `error` field for details. |

#### Result fields (on completion)

| Field | Description |
|-------|-------------|
| `glbUrl` | Direct URL to the generated GLB 3D model file. Generated GLB assets use `assets.wearfits.com`; pass the returned URL unchanged to downstream integrations. |
| `viewerUrl` | URL to preview the model in the WEARFITS 3D viewer. |
| `previewGrid` | URL to a composite preview image showing the model from multiple angles. Generated preview assets use `assets.wearfits.com`. |

#### Recommended polling interval

Poll every **5–10 seconds**. Typical processing times are 2–5 minutes depending on enabled options and queue depth. Do not poll more frequently than every 2 seconds to stay within rate limits.

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

Cancel a running job. The job is terminated immediately. Jobs that have already reached `completed` or `failed` status cannot be cancelled.

#### Path parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `jobId` | `string` | Job identifier to cancel. |

#### Example request

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

#### Response — 200 OK

```json
{
  "cancelled": true
}
```

---

### POST /api/v1/digitization/batches

Submit multiple shoes for bulk 3D digitization processing. This is a secondary endpoint intended for high-volume batch workflows. Jobs submitted via this endpoint are placed in a lower-priority queue (`DIGITIZATION_QUEUE`) separate from the real-time `JOB_QUEUE` used by `POST /api/v1/shoe-3d`.

#### Example request

```bash
curl -X POST https://api.wearfits.com/api/v1/digitization/batches \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "images": ["https://example.com/shoe-a-side.jpg"],
        "options": { "productType": "shoe", "uploadToWearFits": true }
      },
      {
        "images": ["https://example.com/shoe-b-side.jpg"],
        "options": { "productType": "shoe", "uploadToWearFits": true }
      }
    ]
  }'
```

Use `GET /api/v1/jobs/{jobId}` to track each item's progress individually.

Each digitization item may include `longestSideCm` for a bag or other non-shoe product. It is the real length of the longest horizontal side (width or depth, never the height), from 5 to 150 cm with 0.1 cm precision. An out-of-range value returns HTTP **400** for the whole batch, and the field is ignored for shoes. If omitted, the try-on keeps the model's own scale when it is plausible for a bag (between 10 and 100 cm across) and shows it at 40 cm otherwise.

---

### POST /api/v1/wearfits/models/{modelId}/size

Set or remove the real size of a published bag or other non-shoe model. Use the `modelId` from `wearfitsTryOn.modelId` on a completed `shoe-3d` job or digitization batch item. The call is idempotent and changes only a model owned by the account associated with the API key.

#### Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `longestSideCm` | `number \| null` | Yes | Set the real length of the model's longest horizontal side (width or depth, never the height), from 5 to 150 cm with 0.1 cm precision. Send `null` to remove the stored size; the try-on then keeps the model's own scale when it is plausible for a bag (between 10 and 100 cm across) and shows it at 40 cm otherwise. |

#### Example request

```bash
curl -X POST https://api.wearfits.com/api/v1/wearfits/models/abc123/size \\
  -H "X-API-Key: your_api_key" \\
  -H "Content-Type: application/json" \\
  -d '{"longestSideCm":32.5}'
```

#### Response — 200 OK

```json
{
  "success": true,
  "data": {
    "modelId": "abc123",
    "longestSideCm": 32.5
  }
}
```
## Webhook Signatures

If your integration is configured to receive webhook notifications, each webhook payload is signed with HMAC-SHA256 using a secret derived from your API key.

**Signing secret:** `SHA-256(your_api_key)` (hex-encoded string).

### Verification steps

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

2. Sort the webhook JSON body keys alphabetically and serialize to a canonical string:
   ```python
   import json
   canonical = json.dumps(payload, sort_keys=True, separators=(',', ':'))
   ```

3. Compute the HMAC-SHA256 of the canonical string using the signing secret:
   ```python
   import hmac
   signature = hmac.new(
       signing_secret.encode(),
       canonical.encode(),
       hashlib.sha256
   ).hexdigest()
   ```

4. Compare the computed signature with the value in the `X-Webhook-Signature` request header. Use a constant-time comparison to avoid timing attacks:
   ```python
   import hmac
   is_valid = hmac.compare_digest(signature, request.headers['X-Webhook-Signature'])
   ```

---

## Rate Limits

Limits apply per credential per minute for endpoint limits.

| Endpoint category | Limit |
|-------------------|-------|
| Job submission (POST) | 50 requests/min |
| Job status (GET) and published-model size updates (POST) | 500 requests/min |
| File downloads | 300 requests/min |
| Health check | 5000 requests/min |

Requests that exceed an endpoint limit return HTTP **429 Too Many Requests**.

These endpoint limits are approximate and apply per credential (or per IP when unauthenticated) and endpoint; they are not a global account quota. An additional shared per-IP limit applies before authentication to API requests: 6,000 reads, 1,200 writes, 600 uploads, and 3,000 result-file requests per 60 seconds. Multiple users behind the same proxy or NAT can share this allowance. The IP counters are approximate by location, so they are not a strict worldwide quota. The plain health check, static documentation, and CORS preflight are outside this additional limit.

An early IP-limit rejection returns HTTP **429** with `Retry-After: 60` and `X-Request-ID`; a temporary limit-service failure can return HTTP **503** with `X-Request-ID`. `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` describe endpoint limits when available, but are not guaranteed on early 429 or 503 responses. Respect `Retry-After` when present.

> **Note:** Rate limits are 10x higher when using `localhost` or the development API key (`DEV_API_KEY`). This elevated limit applies to local development environments only and is not active in production.

---
## Full Integration Example

The following example demonstrates a complete shoe-3d workflow: uploading a large GLB base file, submitting the generation job, polling until completion, and handling the result.

```bash
# Step 1: Upload a large base GLB (if applicable)
UPLOAD_RESPONSE=$(curl -s -X POST https://api.wearfits.com/api/v1/files/upload \
  -H "X-API-Key: your_api_key" \
  -F "file=@base-model.glb")

GLB_URL=$(echo $UPLOAD_RESPONSE | jq -r '.url')

# Step 2: Submit the shoe-3d generation job
JOB_RESPONSE=$(curl -s -X POST https://api.wearfits.com/api/v1/shoe-3d \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d "{
    \"images\": [\"https://example.com/shoe-side.jpg\"],
    \"glbInput\": \"$GLB_URL\",
    \"options\": {
      \"uploadToWearFits\": true,
      \"enhanceTexture\": true,
      \"genaiQuality\": \"high\"
    }
  }")

JOB_ID=$(echo $JOB_RESPONSE | jq -r '.jobId')
echo "Job submitted: $JOB_ID"

# Step 3: Poll for job completion
while true; do
  STATUS_RESPONSE=$(curl -s "https://api.wearfits.com/api/v1/jobs/$JOB_ID" \
    -H "X-API-Key: your_api_key")

  STATUS=$(echo $STATUS_RESPONSE | jq -r '.status')
  PROGRESS=$(echo $STATUS_RESPONSE | jq -r '.progress')
  echo "Status: $STATUS ($PROGRESS%)"

  if [ "$STATUS" = "completed" ]; then
    GLB_URL=$(echo $STATUS_RESPONSE | jq -r '.result.glbUrl')
    VIEWER_URL=$(echo $STATUS_RESPONSE | jq -r '.result.viewerUrl')
    echo "Done! GLB: $GLB_URL"
    echo "Viewer: $VIEWER_URL"
    break
  elif [ "$STATUS" = "failed" ]; then
    ERROR=$(echo $STATUS_RESPONSE | jq -r '.error')
    echo "Job failed: $ERROR"
    break
  fi

  sleep 10
done
```
