---
title: 2D to 3D Generator — API Reference
product: shoe3d-generator
last_updated: '2026-09-21'
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. 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. |
| `options` | `object` | No | Processing options (see below). |
| `options.uploadToWearFits` | `boolean` | No | After generation, automatically upload the model to the AR platform at `dev.wearfits.com`. |
| `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. Renders multiple views, enhances them with Gemini, then projects the result back onto the model. |
| `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.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. |

#### 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) to Cloudflare R2 storage 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 hosted URL that can be passed 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://storage.example.com/models/shoe.glb",
    "viewerUrl": "https://app.wearfits.com/viewer?object=abc123",
    "previewGrid": "https://storage.example.com/previews/shoe-grid.png"
  }
}
```

#### Response — 200 OK (failed)

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

#### 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. |
| `viewerUrl` | URL to preview the model in the WEARFITS 3D viewer. |
| `previewGrid` | URL to a composite preview image showing the model from multiple angles. |

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

---

## 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 API key per minute.

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

Requests that exceed the limit return HTTP **429 Too Many Requests**.

> **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
```
