{"openapi":"3.1.0","info":{"title":"WEARFITS Virtual Try-On API","version":"1.2.0","description":"AI-powered virtual try-on and digital twin generation service.\n\n## Features\n- **Virtual Fitting Pipeline**: Complete face → digital twin → try-on in one request\n- **Digital Twin Caching**: 30-day cache for faster subsequent requests with same inputs\n- **Virtual Try-On**: Clothing (top, bottom, dress) and shoes try-on\n- **Digital Twin**: Generate personalized full-body models from face + silhouette\n- **Reference Images**: Support for packshot + on-model reference pairs for better results\n\n## Garment Images\nEach garment slot (`topGarment`, `bottomGarment`, `fullBodyGarment`, `shoes`) accepts either a single image URL/base64 string, or an array of 1-2 images:\n- **`[0]`** (required) — **packshot**: the primary product image used for try-on\n- **`[1]`** (optional) — **on-model reference**: an additional photo (e.g. garment worn by a model) to help AI understand fit and draping\n\nOrder matters, naming does not. Examples:\n```json\n\"topGarment\": \"https://example.com/shirt.jpg\"\n\"topGarment\": [\"https://example.com/shirt-packshot.jpg\", \"https://example.com/shirt-on-model.jpg\"]\n```\n\n## Retrying Failed Jobs\nIf a job fails (`job.failed` webhook), you can safely retry by submitting a new request with the same payload. Each submission creates a new job — there is no deduplication. A `job.failed` event means all internal retries and provider fallbacks have been exhausted.\n\n## Authentication\nAll API endpoints require an API key passed via the `X-API-Key` header.\n\nTwo types of API keys are supported:\n1. **Service keys** - For server-to-server integrations (contact WEARFITS for access)\n2. **User keys** - Created at dash.wearfits.com for individual users\n\n## File Access\nResult files require the same API key used to create the job.\nInternal files (cached digital twins) are not accessible via the API.\n\n## Rate Limits\n- Job submission: 50 requests/minute\n- Job status: 500 requests/minute\n- File downloads: 300 requests/minute\n\nRate limit headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`\n\n## Webhooks\nYou can provide a webhook URL when submitting requests to receive notifications when jobs complete.\nWebhooks are signed with HMAC-SHA256 via `X-Webhook-Signature` header using a **per-client signing secret**.\n\n**Your signing secret** is the SHA-256 hash of your API key (hex-encoded): `signing_secret = SHA-256(api_key)`\n\nTo verify: compute HMAC-SHA256 of the canonical JSON body (keys sorted alphabetically) using your signing secret, then compare with the `X-Webhook-Signature` header (strip the `sha256=` prefix).\n\n```python\nimport hmac, hashlib, json\n\ndef verify_webhook(api_key: str, raw_body: bytes, signature_header: str) -> bool:\n    signing_secret = hashlib.sha256(api_key.encode()).hexdigest()\n    payload = json.loads(raw_body)\n    canonical = json.dumps(payload, sort_keys=True, separators=(',', ':'))\n    expected = 'sha256=' + hmac.new(\n        signing_secret.encode(), canonical.encode(), hashlib.sha256\n    ).hexdigest()\n    return hmac.compare_digest(expected, signature_header)\n```","contact":{"name":"WEARFITS Support","url":"https://wearfits.com/contact?s=support"}},"servers":[{"url":"https://api.wearfits.com","description":"Production"}],"tags":[{"name":"Health","description":"Health check endpoints"},{"name":"Virtual Fitting","description":"Complete pipeline: face + silhouette/photo/measurements → digital twin → try-on (with 30-day caching)"},{"name":"Try-On","description":"Virtual try-on for clothing and shoes"},{"name":"Digital Twin","description":"Generate digital twin from face + depth silhouette"},{"name":"Generate Model","description":"Generate full-body model from face photo"},{"name":"Jobs","description":"Job management endpoints"},{"name":"Texture Painter","description":"AI-powered texture enhancement for the browser-based texture painting tool (no auth required)"}],"security":[{"apiKey":[]}],"components":{"schemas":{"HealthResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string","format":"date-time"},"version":{"type":"string"}},"required":["status","timestamp","version"]},"WarmupResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string","format":"date-time"},"modal":{"type":"object","properties":{"warm":{"type":"boolean"},"responseTimeMs":{"type":"number"},"error":{"type":"string"}},"required":["warm"]},"queue":{"type":"object","properties":{"warm":{"type":"boolean"}},"required":["warm"]}},"required":["status","timestamp","modal","queue"]},"QueueWarmupResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string","format":"date-time"},"queueWarm":{"type":"boolean","enum":[true]}},"required":["status","timestamp","queueWarm"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"Invalid request body"},"details":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}},"required":["success","error"]},"JobResult":{"type":"object","properties":{"url":{"type":"string","format":"uri","example":"https://results.wearfits.com/result-123.png"},"expiresAt":{"type":"string","format":"date-time","example":"2024-01-02T12:00:00Z"},"digitalTwinId":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Unique ID for the generated digital twin (SHA-256 hex), can be used in subsequent requests"}},"required":["url","expiresAt"]},"TryOnResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"status":{"type":"string","enum":["queued","completed"]},"estimatedProcessingTime":{"type":"number","description":"Estimated processing time in seconds","example":30},"statusUrl":{"type":"string","format":"uri","description":"URL to check job status","example":"https://api.wearfits.com/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000"},"results":{"type":"array","items":{"$ref":"#/components/schemas/JobResult"},"description":"Results (returned immediately if cached)"}},"required":["success","jobId","status","estimatedProcessingTime","statusUrl"]},"ClothingTryOnRequest":{"type":"object","properties":{"personImages":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":3,"description":"Person photos (URLs or base64) for face and/or silhouette. Required if digitalTwinId is not provided.","example":["https://example.com/person.jpg"]},"digitalTwinId":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Unique identifier for a previously generated digital twin (SHA-256 hex hash)","example":"24e73099501085bae09d26bcdc53624142cac544d5b015c599e498df8a9f6b5e"},"topGarment":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Top garment (shirt, blouse, etc.) - single URL or array of [packshot, on-model-reference]","example":["https://example.com/top-packshot.jpg","https://example.com/top-on-model.jpg"]},"bottomGarment":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Bottom garment (pants, skirt, etc.) - single URL or array of [packshot, on-model-reference]","example":"https://example.com/bottom.jpg"},"fullBodyGarment":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Full-body garment (dress, jumpsuit, etc.) - single URL or array of [packshot, on-model-reference]","example":["https://example.com/dress-packshot.jpg","https://example.com/dress-on-model.jpg"]},"shoes":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Optional shoes to add to any outfit - single URL or array of [packshot, on-model-reference]","example":"https://example.com/heels.jpg"},"options":{"type":"object","properties":{"quality":{"type":"string","enum":["draft","standard","high"],"default":"standard","description":"Output quality level"},"preserveBackground":{"type":"boolean","default":true,"description":"Whether to preserve the original background"},"skipResultCache":{"type":"boolean","default":false,"description":"If true, skip final result cache and always generate fresh try-on"},"provider":{"type":"string","enum":["openrouter","openai","xai"],"description":"Specific provider to use (optional)"},"promptPreset":{"type":"string","enum":["clothing-1"],"default":"clothing-1","description":"Prompt style preset for clothing try-on."}}},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}}},"ShoeTryOnRequest":{"type":"object","properties":{"personImages":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Person/feet photos (URLs or base64). Required if digitalTwinId is not provided.","example":["https://example.com/feet.jpg"]},"digitalTwinId":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Unique identifier for a previously generated digital twin (SHA-256 hex hash)","example":"24e73099501085bae09d26bcdc53624142cac544d5b015c599e498df8a9f6b5e"},"shoeImages":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"angle":{"type":"string","enum":["front","side","back"],"description":"Angle of the shoe image"}},"required":["url"]},"minItems":1,"maxItems":4,"description":"Shoe images (URLs or base64) from various angles","example":[{"url":"https://example.com/shoe-front.jpg","angle":"front"}]},"options":{"type":"object","properties":{"quality":{"type":"string","enum":["draft","standard","high"],"default":"standard"},"provider":{"type":"string","enum":["openrouter","openai","xai"]}}},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}},"required":["shoeImages"]},"GenerateResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued"]},"estimatedProcessingTime":{"type":"number","description":"Estimated time in seconds"},"statusUrl":{"type":"string","format":"uri"}},"required":["success","jobId","status","estimatedProcessingTime","statusUrl"]},"GenerateRequest":{"type":"object","properties":{"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Text prompt describing what to generate or how to modify the image","example":"Generate a red Adidas running shoe on white background"},"inputImages":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":4,"description":"Optional input images to use as reference or to modify","example":["https://example.com/shoe.jpg"]},"options":{"type":"object","properties":{"model":{"type":"string","description":"Model to use for image generation"},"quality":{"type":"string","enum":["draft","standard","high"],"default":"standard"}}},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}},"required":["prompt"]},"DigitalTwinResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","completed"]},"estimatedProcessingTime":{"type":"number","description":"Estimated time in seconds"},"statusUrl":{"type":"string","format":"uri"},"mode":{"type":"string","enum":["direct","photo","measurements","clothing_size"],"description":"Processing mode: direct (pre-rendered silhouette), photo (automatic SAM-3D extraction), measurements (body from measurements), or clothing_size (body from clothing size)"},"digitalTwinId":{"type":"string","description":"Unique ID for the generated digital twin (returned immediately if cached)"}},"required":["success","jobId","status","estimatedProcessingTime","statusUrl","mode"]},"BodyMeasurements":{"type":"object","properties":{"height":{"type":"number","minimum":140,"maximum":210,"description":"Body height in cm (140-210)","example":170},"chest":{"type":"number","minimum":70,"maximum":150,"description":"Chest/bust circumference in cm (70-150)","example":90},"waist":{"type":"number","minimum":55,"maximum":140,"description":"Waist circumference in cm (55-140)","example":70},"hip":{"type":"number","minimum":70,"maximum":150,"description":"Hip circumference in cm (70-150)","example":95},"inseam":{"type":"number","minimum":60,"maximum":95,"description":"Inseam length in cm (60-95)","example":75}},"description":"**[Measurements Mode]** Generate body silhouette from measurements instead of a photo.\n\n**What to provide:** At least 2 body measurements in centimeters:\n- height: Body height (140-210 cm)\n- chest: Chest/bust circumference (70-150 cm)\n- waist: Waist circumference (55-140 cm)\n- hip: Hip circumference (70-150 cm)\n- inseam: Inseam length (60-95 cm)\n\n**What happens:** The API:\n1. Finds the closest matching body from our dataset\n2. Generates a 3D mesh with the matched body shape\n3. Applies the specified pose\n4. Creates the digital twin\n\n**Use this if:** You have body measurement data (e.g., from a sizing profile) but no full-body photo.\n\n**Note:** Results are approximations based on nearest-neighbor matching."},"ClothingSize":{"type":"object","properties":{"height":{"type":"number","minimum":140,"maximum":210,"description":"Body height in cm (140-210)","example":170},"size":{"type":"string","enum":["XS","S","M","L","XL","XXL","3XL"],"description":"Typical clothing size worn (XS, S, M, L, XL, XXL, 3XL)","example":"M"}},"required":["height","size"],"description":"**[Clothing Size Mode]** Generate body from clothing size instead of exact measurements.\n\n**What to provide:**\n- height: Body height in cm (140-210)\n- size: Clothing size (XS, S, M, L, XL, XXL, 3XL)\n\n**What happens:** The API:\n1. Converts size to body measurements using market-average data from major brands\n2. Finds the closest matching body from our dataset\n3. Generates a 3D mesh with the matched body shape\n4. Creates the digital twin\n\n**Use this if:** You only know the user's clothing size, not their exact measurements.\n\n**Note:** Requires `gender` field to be set (defaults to female if not provided).\nMarket averages from: H&M, Uniqlo, ASOS, Nike, Adidas, Zara, Gap, Shein, Boohoo, Lululemon."},"DigitalTwinRequest":{"type":"object","properties":{"faceImage":{"type":"string","description":"Face photo for identity preservation (URL or base64). This should be a well-lit, front-facing photo showing the face clearly. Required for both modes.","example":"https://example.com/face-selfie.jpg"},"bodyPhotoUrl":{"type":"string","description":"**[Photo Mode - RECOMMENDED]** Full-body photo (URL or base64) taken with a camera/phone.\n\n**What to provide:** A normal photograph showing the entire person standing, from head to toe.\n\n**What happens:** The API automatically:\n1. Extracts a 3D body mesh using SAM-3D\n2. Applies a standard pose for try-on\n3. Generates the digital twin\n\n**Requirements:**\n- Must show FULL BODY (head to feet visible)\n- Person should be standing\n- Good lighting, minimal obstructions\n\n**Use this if:** You're building a consumer app where users upload photos from their phone.","example":"https://example.com/user-full-body-photo.jpg"},"poseId":{"type":"string","enum":["standing_arms_down","man_pose","girl_pose","shoe_girl_pose","walking_pose","default"],"default":"default","description":"[Photo Mode] Target pose for the digital twin. Each pose creates a separately cached twin.\n\n**Available poses:**\n- `default`: System default pose (currently girl_pose, configurable)\n- `girl_pose`: Default female standing pose\n- `standing_arms_down`: Natural standing with arms at sides\n- `man_pose`: Default male standing pose\n- `shoe_girl_pose`: Pose optimized for shoe try-on (visible feet)\n\n**Caching:** Same face + body + pose = same cached digitalTwinId. Different poses create different twins.","example":"default"},"silhouetteImage":{"type":"string","description":"**[Direct Mode - Advanced]** Pre-rendered depth/silhouette image (URL or base64) from your own 3D pipeline.\n\n**What to provide:** A depth-rendered visualization of a 3D body mesh (NOT a regular photo).\n\n**What happens:** The API skips body extraction and uses your silhouette directly for twin generation.\n\n**Use this if:** You have your own 3D body scanning/rendering pipeline and want to provide pre-processed silhouettes.\n\n**Note:** Most applications should use Photo Mode (bodyPhotoUrl) instead.","example":"https://example.com/depth-render.png"},"bodyMeasurements":{"$ref":"#/components/schemas/BodyMeasurements"},"clothingSize":{"$ref":"#/components/schemas/ClothingSize"},"gender":{"type":"string","enum":["male","female","neutral"],"description":"Gender for prompt building. If not provided, uses neutral pronoun.","example":"female"},"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Optional additional styling prompt. Will be combined with default digital twin prompt. Use this to customize clothing, background, etc.","example":"wearing casual jeans and white t-shirt, outdoor setting"},"options":{"type":"object","properties":{"quality":{"type":"string","enum":["draft","standard","high"],"default":"standard","description":"Quality tier affects processing time"},"seed":{"type":"integer","description":"Random seed for reproducible generation (not guaranteed with all models)"},"skipCache":{"type":"boolean","default":false,"description":"If true, skip cache and always generate fresh digital twin"},"promptPreset":{"type":"string","enum":["twin-1"],"default":"twin-1","description":"Prompt style preset for digital twin generation."},"preservePose":{"type":"boolean","default":false,"description":"[Photo Mode only] If true, preserve the original pose from bodyPhotoUrl instead of applying a standard pose. Useful when you want the digital twin to match the exact pose in the input photo."},"provider":{"type":"string","enum":["openrouter","openai"],"description":"Image provider override for digital twin generation."}},"description":"Generation options"},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}}},"DigitalTwinStatusResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"digitalTwinId":{"type":"string","description":"The digital twin ID that was checked"},"valid":{"type":"boolean","description":"Whether the digital twin exists and is available for use"},"createdAt":{"type":"string","description":"When the digital twin was created (ISO 8601)"},"expiresAt":{"type":"string","description":"When the digital twin will expire (ISO 8601)"}},"required":["success","digitalTwinId","valid"]},"Generate3DResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued"]},"estimatedProcessingTime":{"type":"number","description":"Estimated time in seconds"},"statusUrl":{"type":"string","format":"uri"}},"required":["success","jobId","status","estimatedProcessingTime","statusUrl"]},"Generate3DRequest":{"type":"object","properties":{"model":{"type":"string","enum":["fal-ai/hunyuan-3d/v3.1/pro/image-to-3d","fal-ai/bytedance/seed3d/image-to-3d","fal-ai/trellis","fal-ai/stable-fast-3d"],"default":"fal-ai/hunyuan-3d/v3.1/pro/image-to-3d","description":"The 3D generation model to use. Optional — a default model is used if omitted."},"inputImageUrl":{"type":"string","format":"uri","description":"URL of the image to convert to 3D","example":"https://example.com/shoe.png"},"options":{"type":"object","properties":{"faceCount":{"type":"number","minimum":40000,"maximum":1500000,"default":50000,"description":"Target polygon count"},"enablePbr":{"type":"boolean","default":true,"description":"Enable PBR material generation"},"generateType":{"type":"string","enum":["Normal","LowPoly","Geometry"],"default":"Normal","description":"Generation type: Normal=textured, LowPoly=reduced, Geometry=white"},"backImageUrl":{"type":"string","format":"uri","description":"Optional rear view image"},"leftImageUrl":{"type":"string","format":"uri","description":"Optional left view image"},"rightImageUrl":{"type":"string","format":"uri","description":"Optional right view image"},"uploadToWearFits":{"type":"boolean","default":false,"description":"Upload the generated 3D model to WEARFITS for shoe try-on"},"rightShoe":{"type":"boolean","default":false,"description":"Specifies if this is a right shoe model (for WEARFITS upload)"}}},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}},"required":["inputImageUrl"]},"Shoe3DResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued"]},"models":{"type":"array","items":{"type":"string"},"description":"Models that will be used for generation"},"estimatedProcessingTime":{"type":"number","description":"Estimated time in seconds (based on slowest model)"},"statusUrl":{"type":"string","format":"uri"}},"required":["success","jobId","status","models","estimatedProcessingTime","statusUrl"]},"ShoeValidationIssue":{"type":"object","properties":{"code":{"type":"string","enum":["BAD_LIGHTING","BUSY_BACKGROUND","MULTIPLE_SHOES","SHOE_NOT_FULL_FRAME","SHOE_CROPPED","REFLECTIVE_LIGHTS","PEOPLE_OR_HANDS","TEXT_OR_WATERMARKS","BLURRY_IMAGE","NOT_A_SHOE","BACK_VIEW_ONLY","SOLE_VIEW_ONLY"],"description":"Issue code identifying the problem"},"message":{"type":"string","description":"Human-readable description of the issue"},"severity":{"type":"string","enum":["error","warning"],"description":"Severity level - errors prevent processing, warnings are informational"}},"required":["code","message","severity"]},"Shoe3DValidationError":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"code":{"type":"string","enum":["SHOE_IMAGE_VALIDATION_FAILED"]},"message":{"type":"string","description":"Human-readable error message","example":"No suitable shoe image found."},"details":{"type":"array","items":{"$ref":"#/components/schemas/ShoeValidationIssue"},"description":"Detailed list of validation issues"}},"required":["code","message","details"]},"validation":{"type":"object","properties":{"isValid":{"type":"boolean","enum":[false]},"bestImageIndex":{"type":"integer","minimum":0},"isRightShoe":{"type":"boolean","nullable":true},"confidence":{"type":"number","minimum":0,"maximum":1},"issues":{"type":"array","items":{"$ref":"#/components/schemas/ShoeValidationIssue"}},"processingTimeMs":{"type":"number"}},"required":["isValid","bestImageIndex","isRightShoe","confidence","issues","processingTimeMs"],"description":"Full validation result for debugging"}},"required":["success","error","validation"]},"ShoePreValidation":{"type":"object","properties":{"bestImageIndex":{"type":"integer","minimum":0,"description":"0-based index of the best image for 3D generation"},"isRightShoe":{"type":"boolean","nullable":true,"description":"Whether the shoe is a right shoe (true), left shoe (false), or unknown (null)"},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Confidence score of the validation (0.0-1.0)"},"issues":{"type":"array","items":{"$ref":"#/components/schemas/ShoeValidationIssue"},"description":"List of warnings found during validation (errors would have rejected the request)"},"processingTimeMs":{"type":"number","description":"Time spent on validation in milliseconds"},"correctionApplied":{"type":"boolean","description":"Whether AI image correction was applied"},"correctedImageUrl":{"type":"string","description":"URL or base64 of the AI-corrected packshot image (if correction was applied)"},"correctionModelUsed":{"type":"string","description":"Actual image-generation model used to create the corrected packshot"},"correctionProcessingTimeMs":{"type":"number","description":"Time spent on image correction in milliseconds"}},"required":["bestImageIndex","isRightShoe"],"description":"Pre-validation result from synchronous image validation. Auto-populated by the API when validation passes. Do not set this manually."},"Shoe3DRequest":{"type":"object","properties":{"images":{"type":"array","items":{"type":"string"},"maxItems":10,"description":"Array of shoe image URLs (1-10). First image is used as primary input for 3D generation. Can be omitted if creating purely from glbInput.","example":["https://example.com/shoe-front.jpg","https://example.com/shoe-side.jpg"]},"models":{"type":"array","items":{"type":"string","enum":["fal-ai/trellis-2","fal-ai/hunyuan-3d/v3.1/pro/image-to-3d","fal-ai/bytedance/seed3d/image-to-3d","fal-ai/meshy/v6-preview/image-to-3d"]},"minItems":1,"maxItems":4,"default":["fal-ai/hunyuan-3d/v3.1/pro/image-to-3d"],"description":"Array of 3D generation models to use. Optional — a default model is used if omitted. Multiple models run in parallel and return separate results."},"glbInput":{"type":"string","description":"Optional URL or base64 data URL to an existing 3D GLB model. If provided, the initial Generative AI 3D model generation step is skipped, and the pipeline immediately proceeds to rendering the 6-angle preview and projecting AI textures onto this model. Since 30MB base64 JSON payloads often hit Cloudflare Worker OOM limits, it is highly recommended to first upload your GLB using POST /api/v1/files/upload and supply the returned URL here.","example":"https://example.com/shoe.glb"},"options":{"type":"object","properties":{"uploadToWearFits":{"type":"boolean","default":false,"description":"Upload the generated 3D model to WEARFITS for shoe try-on"},"validateInQueue":{"type":"boolean","default":true,"description":"If true (default), run image validation and AI correction asynchronously in the queue. The request returns immediately with a job ID, and validation/correction happens during processing. If false, validation runs synchronously and returns 400 if no valid image is found."},"selectBestModel":{"type":"boolean","default":false,"description":"Use postprocessor to automatically select the best 3D model when running multiple models. Currently selects the first successful GLB result (dummy implementation). Future: AI-based quality analysis of mesh topology and texture fidelity."},"skipCache":{"type":"boolean","default":false,"description":"If true, skip cache and always generate fresh 3D model. Cache key is based on input image URL + model name."},"correctImage":{"type":"boolean","default":true,"description":"If true (default), use AI (Gemini 3 Pro) to generate an ideal packshot from the uploaded photo(s) before validation and 3D generation. The generated image will have a clean background, proper framing, and studio-quality lighting. This can improve 3D generation results for user photos that have suboptimal backgrounds or lighting. Set to false to skip correction."},"preValidation":{"$ref":"#/components/schemas/ShoePreValidation"},"enhanceTexture":{"type":"boolean","default":true,"description":"Enhance GLB texture using AI with input images as references. Renders views (4 default, 6 in high quality), sends to Gemini for enhancement, and projects back onto GLB. Runs after simplification, before WEARFITS upload. Falls back to original GLB on failure. Enabled by default."},"refineLogo":{"type":"boolean","default":false,"description":"Run a second AI pass focused on fixing logos, brand text, and emblems after texture enhancement. Requires enhanceTexture to be enabled. Works with both 4-view and 6-view modes."},"genaiQuality":{"type":"string","enum":["default","high"],"default":"high","description":"AI model quality for image correction and texture enhancement. Defaults to \"high\". \"default\" uses Gemini Flash (faster, cheaper) with 4-view texture enhancement. \"high\" uses Gemini Pro for packshot correction (better quality, slower) and 6-view texture enhancement for full coverage (4 sides + top + bottom). Both have fallback chains for reliability."},"sessionId":{"type":"string","description":"Optional session ID to be passed to WEARFITS as token during shoe upload. Used for compatibility with frontend apps that have user sessions, ensuring uploaded objects are correctly assigned to the user in their WEARFITS account."},"productType":{"type":"string","enum":["shoe","other","auto"],"default":"shoe","description":"\"shoe\" (default): shoe-specific prompts and orientation. \"other\": generic product prompts, no shoe-specific orientation or materials. \"auto\": quick AI detection from the uploaded images — any footwear detected uses shoe pipeline, everything else uses generic."}}},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}}},"VirtualFittingResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","completed"]},"estimatedProcessingTime":{"type":"number","description":"Estimated time in seconds"},"statusUrl":{"type":"string","format":"uri"},"mode":{"type":"string","enum":["direct","photo","measurements","clothing_size","twin_id"],"description":"Processing mode used for the request"},"twinCached":{"type":"boolean","description":"Whether the digital twin was retrieved from cache (faster processing)"},"results":{"type":"array","items":{"$ref":"#/components/schemas/JobResult"},"description":"Results (returned immediately if cached)"}},"required":["success","jobId","status","estimatedProcessingTime","statusUrl","mode","twinCached"]},"VirtualFittingRequest":{"type":"object","properties":{"faceImage":{"type":"string","description":"[Direct Mode] Face/head photo for identity preservation (URL or base64). Required if using silhouetteImage.","example":"https://example.com/face.jpg"},"silhouetteImage":{"type":"string","description":"[Direct Mode] Body silhouette/depth image from pose transfer (URL or base64). Required if using faceImage.","example":"https://example.com/silhouette.png"},"photoUrl":{"type":"string","description":"[Photo Mode] Full-body photo (URL or base64) showing the entire person from head to toe. The system will extract body mesh and generate silhouette. IMPORTANT: Must be a full-body photo, not just face/headshot - body extraction requires visible body to extract mesh. Required if using poseId.","example":"https://example.com/full-body-photo.jpg"},"poseId":{"type":"string","enum":["standing_arms_down","man_pose","girl_pose","shoe_girl_pose","walking_pose","default"],"default":"default","description":"[Photo Mode Only] Pose to apply when generating a new digital twin from photoUrl. Ignored when using digitalTwinId (the twin already has its pose baked in). To use a different pose with the same person, create a new digital twin with the desired poseId. Use \"default\" for the system default pose.","example":"default"},"bodyMeasurements":{"allOf":[{"$ref":"#/components/schemas/BodyMeasurements"},{"description":"**[Measurements Mode]** Generate body silhouette from measurements instead of a photo.\n\n**What to provide:** At least 2 body measurements in centimeters:\n- height: Body height (140-210 cm)\n- chest: Chest/bust circumference (70-150 cm)\n- waist: Waist circumference (55-140 cm)\n- hip: Hip circumference (70-150 cm)\n- inseam: Inseam length (60-95 cm)\n\n**What happens:** The API:\n1. Finds the closest matching body from our dataset\n2. Generates a 3D mesh with the matched body shape\n3. Applies the specified pose\n4. Creates the digital twin\n5. Applies garments to the model\n\n**Use this if:** You have body measurement data (e.g., from a sizing profile) but no full-body photo.\n\n**Note:** Results are approximations based on nearest-neighbor matching."}]},"clothingSize":{"allOf":[{"$ref":"#/components/schemas/ClothingSize"},{"description":"**[Clothing Size Mode]** Generate body from clothing size instead of exact measurements.\n\n**What to provide:**\n- height: Body height in cm (140-210)\n- size: Clothing size (XS, S, M, L, XL, XXL, 3XL)\n\n**What happens:** The API:\n1. Converts size to body measurements using market-average data from major brands\n2. Finds the closest matching body from our dataset\n3. Generates a 3D mesh with the matched body shape\n4. Creates the digital twin\n5. Applies garments to the model\n\n**Use this if:** You only know the user's clothing size, not their exact measurements.\n\n**Note:** Requires `gender` field to be set (defaults to female if not provided).\nMarket averages from: H&M, Uniqlo, ASOS, Nike, Adidas, Zara, Gap, Shein, Boohoo, Lululemon."}]},"digitalTwinId":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Unique identifier for a previously generated digital twin (SHA-256 hex hash)","example":"24e73099501085bae09d26bcdc53624142cac544d5b015c599e498df8a9f6b5e"},"gender":{"type":"string","enum":["male","female","neutral"],"description":"Gender for prompt building. If not provided, uses neutral pronoun.","example":"female"},"topGarment":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Top garment (shirt, blouse, etc.) - single URL or array of [packshot, on-model-reference]","example":["https://example.com/top-packshot.jpg","https://example.com/top-on-model.jpg"]},"bottomGarment":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Bottom garment (pants, skirt, etc.) - single URL or array of [packshot, on-model-reference]","example":"https://example.com/bottom.jpg"},"fullBodyGarment":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Full-body garment (dress, jumpsuit, etc.) - single URL or array of [packshot, on-model-reference]","example":["https://example.com/dress-packshot.jpg","https://example.com/dress-on-model.jpg"]},"shoes":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":2,"description":"Array of 1-2 images (URLs or base64): first is packshot, second (optional) is on-model reference","example":["https://example.com/packshot.jpg","https://example.com/on-model.jpg"]}],"description":"Optional shoes to add to any outfit - single URL or array of [packshot, on-model-reference]","example":"https://example.com/heels.jpg"},"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Optional additional styling prompt. Will be combined with default digital twin prompt. Use this to customize clothing, background, etc.","example":"wearing casual jeans and white t-shirt, outdoor setting"},"options":{"type":"object","properties":{"quality":{"type":"string","enum":["draft","standard","high"],"default":"standard","description":"Quality tier affects processing time"},"seed":{"type":"integer","description":"Random seed for reproducible generation (not guaranteed with all models)"},"skipCache":{"type":"boolean","default":false,"description":"If true, skip digital twin cache and always generate fresh"},"skipResultCache":{"type":"boolean","default":false,"description":"If true, skip final result cache and always generate fresh try-on"},"preservePose":{"type":"boolean","default":false,"description":"[Photo Mode only] If true, preserve the original pose from photoUrl instead of applying a standard pose."},"provider":{"type":"string","enum":["openrouter","openai"],"description":"Image provider override for both digital twin and try-on steps."}},"description":"Generation options"},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}}},"PoseTransferResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","completed"]},"estimatedProcessingTime":{"type":"number","description":"Estimated time in seconds"},"statusUrl":{"type":"string","format":"uri"},"mode":{"type":"string","enum":["cached_pose","image_pose"],"description":"Processing mode: cached_pose (using poseId) or image_pose (extracting from poseImageUrl)"},"digitalTwinId":{"type":"string","description":"Unique ID for the generated digital twin (returned immediately if cached)"}},"required":["success","jobId","status","estimatedProcessingTime","statusUrl","mode"]},"PoseTransferRequest":{"type":"object","properties":{"sourceImageUrl":{"type":"string","format":"uri","description":"URL of the source person's full-body photo. This person's body shape and face will be preserved in the output.\n\n**Requirements:**\n- Must show FULL BODY (head to feet visible)\n- Person should be standing\n- Good lighting, minimal obstructions","example":"https://example.com/person-a-fullbody.jpg"},"poseImageUrl":{"type":"string","format":"uri","description":"URL of a reference person's photo whose pose you want to copy.\n\n**Requirements:**\n- Must show FULL BODY (head to feet visible)\n- Clear pose visible\n- Can be any person - only the pose is extracted\n\n**Processing:** Adds ~20s for pose extraction via SAM-3D.","example":"https://example.com/person-b-pose-reference.jpg"},"poseId":{"type":"string","enum":["standing_arms_down","man_pose","girl_pose","shoe_girl_pose","walking_pose","default"],"description":"Pre-defined pose ID to apply. Faster than poseImageUrl since no extraction needed.\n\n**Available poses:**\n- `default`: System default pose (currently girl_pose, configurable)\n- `standing_arms_down`: Natural standing pose with arms relaxed at sides\n- `man_pose`: Male standing pose\n- `girl_pose`: Female standing pose\n- `shoe_girl_pose`: Pose optimized for shoe try-on","example":"default"},"gender":{"type":"string","enum":["male","female","neutral"],"description":"Gender for prompt building when generating the final image. If not provided, uses neutral.","example":"female"},"options":{"type":"object","properties":{"quality":{"type":"string","enum":["draft","standard","high"],"default":"standard","description":"Quality tier affects processing time and output resolution"},"seed":{"type":"integer","description":"Random seed for reproducible generation (not guaranteed with all models)"},"skipCache":{"type":"boolean","default":false,"description":"If true, skip cache and always generate fresh result"},"includeGlb":{"type":"boolean","default":true,"description":"If true, include the transformed GLB file in results"},"includeVisualization":{"type":"boolean","default":true,"description":"If true, include a mesh visualization PNG in results"}},"description":"Generation options"},"webhookUrl":{"type":"string","format":"uri","description":"Webhook URL (HTTPS only) to notify when job is complete","example":"https://your-app.com/webhooks/wearfits"}},"required":["sourceImageUrl"]},"WearFitsTryOn":{"type":"object","properties":{"modelId":{"type":"string","description":"WEARFITS model ID"},"colorId":{"type":"string","description":"WEARFITS color ID"},"viewerUrl":{"type":"string","format":"uri","description":"URL to the WEARFITS AR try-on viewer"}},"required":["modelId","colorId","viewerUrl"]},"Shoe3DModelResult":{"type":"object","properties":{"model":{"type":"string","description":"Identifier of the model that generated this result"},"status":{"type":"string","enum":["queued","processing","completed","failed"],"description":"Status of this specific model generation"},"glbUrl":{"type":"string","format":"uri","description":"URL to the generated GLB 3D model file"},"zipUrl":{"type":"string","format":"uri","description":"URL to the generated ZIP archive (for models that output zip)"},"thumbnailUrl":{"type":"string","format":"uri","description":"URL to a thumbnail preview image"},"renderGridUrl":{"type":"string","format":"uri","description":"6-angle preview grid image URL for quality inspection"},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"description":"Error details if generation failed"},"timeMs":{"type":"number","description":"Generation time in milliseconds"},"providerJobId":{"type":"string","description":"Provider-specific job ID for tracking"},"statusUrl":{"type":"string","description":"Provider status URL for checking generation progress"},"enhancedGlbUrl":{"type":"string","format":"uri","description":"URL to AI texture-enhanced GLB (if enhanceTexture was enabled)"},"textureEnhancementError":{"type":"string","description":"Error message if texture enhancement failed (original GLB used as fallback)"},"logoGridOutputUrl":{"type":"string","format":"uri","description":"URL to logo-refined grid image (if refineLogo was enabled)"},"logoModelUsed":{"type":"string","description":"AI model used for logo refinement pass"}},"required":["model","status"]},"JobError":{"type":"object","properties":{"code":{"type":"string","example":"PROVIDER_ERROR"},"message":{"type":"string","example":"External provider returned an error"},"retryable":{"type":"boolean","example":true}},"required":["code","message","retryable"]},"JobProgress":{"type":"object","properties":{"stage":{"type":"string","example":"processing"},"percentage":{"type":"number","minimum":0,"maximum":100,"example":50}},"required":["stage"]},"JobStatusResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","validating","processing","uploading","completed","failed"]},"mode":{"type":"string","enum":["clothing","shoes","generate","generate-3d","shoe-3d","digital-twin","virtual-fitting","pose-transfer"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"results":{"type":"array","items":{"$ref":"#/components/schemas/JobResult"}},"digitalTwinId":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Unique ID for the generated digital twin (SHA-256 hex)"},"wearfitsTryOn":{"$ref":"#/components/schemas/WearFitsTryOn"},"wearfitsError":{"type":"string"},"shoe3dModelResults":{"type":"array","items":{"$ref":"#/components/schemas/Shoe3DModelResult"},"description":"Per-model results for shoe-3d jobs (includes enhancedGlbUrl if texture enhancement was enabled)"},"error":{"$ref":"#/components/schemas/JobError"},"progress":{"$ref":"#/components/schemas/JobProgress"}},"required":["id","status","mode","createdAt","updatedAt"]},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]}},"required":["success"]},"TraceAttempt":{"type":"object","properties":{"attempt":{"type":"integer","minimum":0,"exclusiveMinimum":true,"description":"Attempt number (1-indexed)","example":1},"durationMs":{"type":"integer","minimum":0,"description":"Duration of this attempt in milliseconds","example":2500},"success":{"type":"boolean","description":"Whether this attempt succeeded","example":true},"error":{"type":"string","description":"Error message if attempt failed","example":"fetch failed"}},"required":["attempt","durationMs","success"]},"TraceEntry":{"type":"object","properties":{"id":{"type":"string","description":"Unique operation ID within this trace","example":"op_1"},"operation":{"type":"string","description":"Operation name","example":"image_validation"},"startedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when operation started","example":"2024-01-15T12:00:00.100Z"},"endedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when operation ended","example":"2024-01-15T12:00:03.500Z"},"durationMs":{"type":"integer","minimum":0,"description":"Total duration in milliseconds","example":3400},"status":{"type":"string","enum":["started","success","failed","skipped"],"description":"Operation status","example":"success"},"input":{"type":"object","additionalProperties":{"nullable":true},"description":"Sanitized input parameters","example":{"imageCount":2,"correctImage":true}},"output":{"type":"object","additionalProperties":{"nullable":true},"description":"Sanitized output (URLs only, no data)","example":{"isValid":true,"bestIndex":0}},"error":{"type":"object","properties":{"code":{"type":"string","example":"TIMEOUT"},"message":{"type":"string","example":"Operation timed out after 180000ms"}},"required":["code","message"],"description":"Error details if operation failed"},"attempts":{"type":"array","items":{"$ref":"#/components/schemas/TraceAttempt"},"description":"Individual retry attempts"}},"required":["id","operation","startedAt","status"]},"TraceSummary":{"type":"object","properties":{"totalOperations":{"type":"integer","minimum":0,"description":"Total number of operations recorded","example":12},"successCount":{"type":"integer","minimum":0,"description":"Number of successful operations","example":11},"failureCount":{"type":"integer","minimum":0,"description":"Number of failed operations","example":1},"skippedCount":{"type":"integer","minimum":0,"description":"Number of skipped operations","example":0},"totalRetries":{"type":"integer","minimum":0,"description":"Total number of retry attempts across all operations","example":2}},"required":["totalOperations","successCount","failureCount","skippedCount","totalRetries"],"description":"Summary statistics"},"TraceLogResponse":{"type":"object","properties":{"jobId":{"type":"string","format":"uuid","description":"Job ID this trace belongs to","example":"550e8400-e29b-41d4-a716-446655440000"},"startedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when processing started","example":"2024-01-15T12:00:00.000Z"},"completedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when processing completed","example":"2024-01-15T12:02:35.000Z"},"totalDurationMs":{"type":"integer","minimum":0,"description":"Total processing duration in milliseconds","example":155000},"status":{"type":"string","enum":["processing","completed","failed"],"description":"Overall trace status","example":"completed"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/TraceEntry"},"description":"Ordered list of operation entries"},"summary":{"$ref":"#/components/schemas/TraceSummary"}},"required":["jobId","startedAt","status","entries","summary"]},"TextureEnhanceResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"imageUrl":{"type":"string","description":"Enhanced texture image as base64 data URL"}},"required":["success","imageUrl"]},"TextureEnhanceRequest":{"type":"object","properties":{"renderImage":{"type":"string","description":"Current texture render as base64 data URL"},"referenceImages":{"type":"array","items":{"type":"string","description":"Image as base64 data URL (data:image/png;base64,...), max 10 MB"},"minItems":1,"maxItems":8,"description":"Reference packshot images (1-8) as base64 data URLs"},"mode":{"type":"string","enum":["single","grid"],"default":"single","description":"Enhancement mode: single view or 2x2 grid of 4 views"},"model":{"type":"string","description":"OpenRouter model ID to use for enhancement."}},"required":["renderImage","referenceImages"]}},"parameters":{}},"paths":{"/health":{"get":{"tags":["Health"],"summary":"Health check","description":"Returns the health status of the API","responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/health/warmup":{"get":{"tags":["Health"],"summary":"Warm up all external services","description":"Warms up Modal workers and Cloudflare queue consumer. Call this when a user enters the try-on flow to reduce cold start latency. Both warmups run in parallel.","responses":{"200":{"description":"Warmup completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WarmupResponse"}}}}}}},"/health/queue-warmup":{"get":{"tags":["Health"],"summary":"Warm up queue consumer only (use /warmup instead)","description":"Enqueues a lightweight message to keep the queue consumer warm. **Prefer using /health/warmup** which warms both Modal and queue in a single call.","responses":{"200":{"description":"Queue warmup message enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueWarmupResponse"}}}}}}},"/files/signed":{"get":{"tags":["Files"],"summary":"Get a signed file","description":"Retrieve a file using a signed URL. No API key required for valid signed URLs.","parameters":[{"schema":{"type":"string","description":"File key (path)"},"required":true,"name":"key","in":"query"},{"schema":{"type":"string","description":"HMAC token"},"required":true,"name":"token","in":"query"},{"schema":{"type":"string","description":"Expiry timestamp"},"required":true,"name":"expires","in":"query"}],"responses":{"200":{"description":"File content","content":{"image/png":{"schema":{"nullable":true}},"image/jpeg":{"schema":{"nullable":true}}}},"403":{"description":"Invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/{key}":{"get":{"tags":["Files"],"summary":"Get a result file","description":"Retrieve a generated result file from storage. Requires the same API key used to create the job. Files expire after 24 hours.","parameters":[{"schema":{"type":"string","description":"File key (path)"},"required":true,"name":"key","in":"path"}],"responses":{"200":{"description":"File content","content":{"image/png":{"schema":{"nullable":true}},"image/jpeg":{"schema":{"nullable":true}},"image/webp":{"schema":{"nullable":true}},"model/gltf-binary":{"schema":{"nullable":true}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/results/{jobId}/{filename}":{"get":{"tags":["Files"],"summary":"Get a result file by job ID","description":"Retrieve a generated result file from storage using job ID and filename. Requires the same API key used to create the job.","parameters":[{"schema":{"type":"string","format":"uuid","description":"Job ID"},"required":true,"name":"jobId","in":"path"},{"schema":{"type":"string","description":"Filename"},"required":true,"name":"filename","in":"path"}],"responses":{"200":{"description":"File content","content":{"image/png":{"schema":{"nullable":true}},"image/jpeg":{"schema":{"nullable":true}},"image/webp":{"schema":{"nullable":true}},"model/gltf-binary":{"schema":{"nullable":true}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/monitoring/api/data":{"get":{"tags":["Monitoring"],"summary":"Get monitoring data","description":"Returns recent digital twin and try-on generation data for the dashboard","parameters":[{"schema":{"type":"string","enum":["twins","tryons","pose-transfers","shoe3ds"],"description":"Optional tab filter to limit results","example":"tryons"},"required":false,"name":"tab","in":"query"}],"responses":{"200":{"description":"Monitoring data","content":{"application/json":{"schema":{"type":"object","properties":{"digitalTwins":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["digital-twin"]},"timestamp":{"type":"string"},"mode":{"type":"string","enum":["photo","direct","measurements","clothing_size"]},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"digitalTwinId":{"type":"string"},"timings":{"type":"object","properties":{"queueMs":{"type":"number"},"sam3dMs":{"type":"number"},"poseMs":{"type":"number"},"twinMs":{"type":"number"},"uploadMs":{"type":"number"},"totalMs":{"type":"number"},"measurementsMs":{"type":"number"}}},"inputs":{"type":"object","properties":{"faceImage":{"type":"string"},"bodyPhoto":{"type":"string"},"silhouette":{"type":"string"},"bodyMeasurements":{"type":"object","additionalProperties":{"type":"number"}}},"required":["faceImage"]},"outputs":{"type":"array","items":{"type":"object","properties":{"model":{"type":"string"},"provider":{"type":"string"},"textureGenModel":{"type":"string"},"status":{"type":"string","enum":["completed","failed","processing","queued"]},"resultUrl":{"type":"string"},"renderGridUrl":{"type":"string"},"timeMs":{"type":"number"},"error":{"type":"string"},"fileSize":{"type":"number"},"meshStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedGlbUrl":{"type":"string"},"textureGridInputUrl":{"type":"string"},"textureGridOutputUrl":{"type":"string"},"enhancedGlbUrl":{"type":"string"},"wearfitsGlbSize":{"type":"number"},"logoGridOutputUrl":{"type":"string"},"logoModelUsed":{"type":"string"},"logoRefineError":{"type":"string"},"logoRefineProcessing":{"type":"boolean"},"numViews":{"type":"number"},"arTryonError":{"type":"string"},"wearfitsTryOn":{"type":"object","properties":{"modelId":{"type":"string"},"colorId":{"type":"string"},"viewerUrl":{"type":"string"}},"required":["modelId","colorId","viewerUrl"]}},"required":["model","provider","status","timeMs"]}},"finalResult":{"type":"string"}},"required":["id","type","timestamp","mode","inputs","outputs"]}},"tryons":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["tryon"]},"timestamp":{"type":"string"},"mode":{"type":"string","enum":["clothing","shoes"]},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"timings":{"type":"object","properties":{"queueMs":{"type":"number"},"validationMs":{"type":"number"},"gridMs":{"type":"number"},"providerMs":{"type":"number"},"uploadMs":{"type":"number"},"totalMs":{"type":"number"},"twinMs":{"type":"number"},"tryonMs":{"type":"number"}}},"inputs":{"type":"object","properties":{"avatar":{"type":"string"},"digitalTwinId":{"type":"string"},"garments":{"type":"array","items":{"type":"string"}},"garmentGrid":{"type":"string"},"poseImage":{"type":"string","description":"Optional pose reference image (OpenPose skeleton) for guided generation"}},"required":["avatar","garments"]},"outputs":{"type":"array","items":{"type":"object","properties":{"model":{"type":"string"},"provider":{"type":"string"},"textureGenModel":{"type":"string"},"status":{"type":"string","enum":["completed","failed","processing","queued"]},"resultUrl":{"type":"string"},"renderGridUrl":{"type":"string"},"timeMs":{"type":"number"},"error":{"type":"string"},"fileSize":{"type":"number"},"meshStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedGlbUrl":{"type":"string"},"textureGridInputUrl":{"type":"string"},"textureGridOutputUrl":{"type":"string"},"enhancedGlbUrl":{"type":"string"},"wearfitsGlbSize":{"type":"number"},"logoGridOutputUrl":{"type":"string"},"logoModelUsed":{"type":"string"},"logoRefineError":{"type":"string"},"logoRefineProcessing":{"type":"boolean"},"numViews":{"type":"number"},"arTryonError":{"type":"string"},"wearfitsTryOn":{"type":"object","properties":{"modelId":{"type":"string"},"colorId":{"type":"string"},"viewerUrl":{"type":"string"}},"required":["modelId","colorId","viewerUrl"]}},"required":["model","provider","status","timeMs"]}},"finalResult":{"type":"string"}},"required":["id","type","timestamp","mode","inputs","outputs"]}},"poseTransfers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["pose-transfer"]},"timestamp":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"inputs":{"type":"object","properties":{"sourceImage":{"type":"string"},"poseImage":{"type":"string"},"poseId":{"type":"string"}},"required":["sourceImage"]},"outputs":{"type":"array","items":{"type":"object","properties":{"model":{"type":"string"},"provider":{"type":"string"},"textureGenModel":{"type":"string"},"status":{"type":"string","enum":["completed","failed","processing","queued"]},"resultUrl":{"type":"string"},"renderGridUrl":{"type":"string"},"timeMs":{"type":"number"},"error":{"type":"string"},"fileSize":{"type":"number"},"meshStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedGlbUrl":{"type":"string"},"textureGridInputUrl":{"type":"string"},"textureGridOutputUrl":{"type":"string"},"enhancedGlbUrl":{"type":"string"},"wearfitsGlbSize":{"type":"number"},"logoGridOutputUrl":{"type":"string"},"logoModelUsed":{"type":"string"},"logoRefineError":{"type":"string"},"logoRefineProcessing":{"type":"boolean"},"numViews":{"type":"number"},"arTryonError":{"type":"string"},"wearfitsTryOn":{"type":"object","properties":{"modelId":{"type":"string"},"colorId":{"type":"string"},"viewerUrl":{"type":"string"}},"required":["modelId","colorId","viewerUrl"]}},"required":["model","provider","status","timeMs"]}},"finalResult":{"type":"string"}},"required":["id","type","timestamp","inputs","outputs"]}},"shoe3ds":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["shoe-3d"]},"timestamp":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"error":{"type":"string"},"timings":{"type":"object","properties":{"queueMs":{"type":"number"},"totalMs":{"type":"number"}}},"inputs":{"type":"object","properties":{"images":{"type":"array","items":{"type":"string"}},"models":{"type":"array","items":{"type":"string"}},"packshotGenModel":{"type":"string"}},"required":["images","models"]},"outputs":{"type":"array","items":{"type":"object","properties":{"model":{"type":"string"},"provider":{"type":"string"},"textureGenModel":{"type":"string"},"status":{"type":"string","enum":["completed","failed","processing","queued"]},"resultUrl":{"type":"string"},"renderGridUrl":{"type":"string"},"timeMs":{"type":"number"},"error":{"type":"string"},"fileSize":{"type":"number"},"meshStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedStats":{"type":"object","properties":{"vertices":{"type":"number"},"triangles":{"type":"number"},"fileSizeBytes":{"type":"number"}},"required":["vertices","triangles","fileSizeBytes"]},"simplifiedGlbUrl":{"type":"string"},"textureGridInputUrl":{"type":"string"},"textureGridOutputUrl":{"type":"string"},"enhancedGlbUrl":{"type":"string"},"wearfitsGlbSize":{"type":"number"},"logoGridOutputUrl":{"type":"string"},"logoModelUsed":{"type":"string"},"logoRefineError":{"type":"string"},"logoRefineProcessing":{"type":"boolean"},"numViews":{"type":"number"},"arTryonError":{"type":"string"},"wearfitsTryOn":{"type":"object","properties":{"modelId":{"type":"string"},"colorId":{"type":"string"},"viewerUrl":{"type":"string"}},"required":["modelId","colorId","viewerUrl"]}},"required":["model","provider","status","timeMs"]}},"wearfitsTryOn":{"type":"object","properties":{"modelId":{"type":"string"},"colorId":{"type":"string"},"viewerUrl":{"type":"string"}},"required":["modelId","colorId","viewerUrl"]}},"required":["id","type","timestamp","inputs","outputs"]}},"lastUpdated":{"type":"string"}},"required":["digitalTwins","tryons","poseTransfers","shoe3ds","lastUpdated"]}}}},"403":{"description":"Access denied - IP not allowed"}}}},"/monitoring/api/tryons/{id}":{"delete":{"tags":["Monitoring"],"summary":"Delete try-on monitoring entry","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Try-on entry deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry not found"}}}},"/monitoring/api/tryons/{id}/outputs/{index}":{"delete":{"tags":["Monitoring"],"summary":"Delete try-on output","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Output index"},"required":true,"name":"index","in":"path"}],"responses":{"200":{"description":"Try-on output deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry or output not found"}}}},"/monitoring/api/digital-twins/{id}":{"delete":{"tags":["Monitoring"],"summary":"Delete digital twin monitoring entry","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Digital twin entry deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry not found"}}}},"/monitoring/api/digital-twins/{id}/outputs/{index}":{"delete":{"tags":["Monitoring"],"summary":"Delete digital twin output","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Output index"},"required":true,"name":"index","in":"path"}],"responses":{"200":{"description":"Digital twin output deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry or output not found"}}}},"/monitoring/api/pose-transfers/{id}":{"delete":{"tags":["Monitoring"],"summary":"Delete pose transfer monitoring entry","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Pose transfer entry deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry not found"}}}},"/monitoring/api/pose-transfers/{id}/outputs/{index}":{"delete":{"tags":["Monitoring"],"summary":"Delete pose transfer output","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Output index"},"required":true,"name":"index","in":"path"}],"responses":{"200":{"description":"Pose transfer output deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry or output not found"}}}},"/monitoring/api/shoe3ds/{id}":{"delete":{"tags":["Monitoring"],"summary":"Delete shoe 3D monitoring entry","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Shoe 3D entry deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry not found"}}}},"/monitoring/api/shoe3ds/{id}/outputs/{index}":{"delete":{"tags":["Monitoring"],"summary":"Delete shoe 3D output","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Output index"},"required":true,"name":"index","in":"path"}],"responses":{"200":{"description":"Shoe 3D output deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"deletedFiles":{"type":"number"}},"required":["success","deletedFiles"]}}}},"404":{"description":"Entry or output not found"}}}},"/monitoring/api/shoe3ds/{id}/outputs/{index}/ar-tryon":{"post":{"tags":["Monitoring"],"summary":"Generate AR try-on from shoe 3D output","description":"Uploads a completed shoe 3D GLB to WEARFITS for AR try-on. Returns immediately with processing status.","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Output index"},"required":true,"name":"index","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"rightShoe":{"type":"boolean","default":false,"description":"Whether this is a right shoe model (default: false/left)"}}}}}},"responses":{"200":{"description":"AR try-on completed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"modelId":{"type":"string","description":"WEARFITS model ID"},"colorId":{"type":"string","description":"WEARFITS color variant ID"},"viewerUrl":{"type":"string","description":"URL to view shoe in AR try-on"}},"required":["success","modelId","colorId","viewerUrl"]}}}},"202":{"description":"AR try-on upload accepted and processing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"status":{"type":"string","enum":["processing"],"description":"Processing status - upload runs asynchronously"},"message":{"type":"string","description":"Status message"}},"required":["success","status","message"]}}}},"400":{"description":"No GLB available or invalid output"},"404":{"description":"Entry or output not found"}}}},"/monitoring/api/shoe3ds/{id}/outputs/{index}/logo-refine":{"post":{"tags":["Monitoring"],"summary":"Run logo refinement on shoe 3D texture","description":"Runs standalone logo refinement (second AI pass) on a previously enhanced texture grid. Synchronous — waits for Modal to complete (~90-180s).","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Output index"},"required":true,"name":"index","in":"path"}],"responses":{"200":{"description":"Logo refinement completed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"logoGridOutputUrl":{"type":"string","description":"URL to logo-refined grid image"},"logoModelUsed":{"type":"string","description":"AI model used for logo refinement"}},"required":["success"]}}}},"400":{"description":"No texture grid available or missing data"},"404":{"description":"Entry or output not found"},"500":{"description":"Logo refinement failed"}}}},"/monitoring/api/regenerate-models":{"get":{"tags":["Monitoring"],"summary":"Get available regeneration models","description":"Returns the list of models available for regenerating try-on outputs","responses":{"200":{"description":"Available models","content":{"application/json":{"schema":{"type":"object","properties":{"openrouter":{"type":"array","items":{"type":"string"}},"fal":{"type":"array","items":{"type":"string"}},"openai":{"type":"array","items":{"type":"string"}},"xai":{"type":"array","items":{"type":"string"}}},"required":["openrouter","fal","openai","xai"]}}}}}}},"/monitoring/api/available-poses":{"get":{"tags":["Monitoring"],"summary":"Get available pose references (experimental)","description":"Returns the list of pose images/templates available for guided regeneration","responses":{"200":{"description":"Available poses","content":{"application/json":{"schema":{"type":"object","properties":{"poses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"imageUrl":{"type":"string"}},"required":["id","name"]}}},"required":["poses"]}}}}}}},"/monitoring/api/tryons/{id}/regenerate":{"post":{"tags":["Monitoring"],"summary":"Regenerate try-on with different model","description":"Generates a new output for an existing try-on using a different model. Returns immediately with 202 Accepted while processing continues in background.","parameters":[{"schema":{"type":"string","description":"Monitoring entry ID"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"Model to use for regeneration","example":"google/gemini-3.1-flash-image-preview"},"prompt":{"type":"string","description":"Optional custom prompt for regeneration"},"poseImage":{"type":"string","description":"Optional pose reference image URL (OpenPose skeleton) to guide generation pose","example":"https://example.com/pose-skeleton.png"}},"required":["model"]}}}},"responses":{"202":{"description":"Regeneration accepted and processing in background","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"outputIndex":{"type":"number","description":"Index of the new output in the outputs array"},"status":{"type":"string","enum":["processing"],"description":"Processing status - regeneration runs asynchronously"}},"required":["success","outputIndex","status"]}}}},"400":{"description":"Invalid model or missing required data"},"404":{"description":"Entry not found"}}}},"/monitoring/api/saas/users":{"get":{"tags":["Monitoring"],"summary":"Get SaaS users","description":"Returns list of users with API key counts and package info from the database","responses":{"200":{"description":"SaaS users data","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"package":{"type":"string"},"createdAt":{"type":"string"},"apiKeysCount":{"type":"number"}},"required":["id","email","name","package","createdAt","apiKeysCount"]}},"stats":{"type":"object","properties":{"total":{"type":"number"},"byPackage":{"type":"object","additionalProperties":{"type":"number"}}},"required":["total","byPackage"]},"error":{"type":"string"}},"required":["users","stats"]}}}},"403":{"description":"Access denied - IP not allowed"}}}},"/monitoring":{"get":{"tags":["Monitoring"],"summary":"Monitoring dashboard","description":"HTML dashboard for viewing generation results","responses":{"200":{"description":"Dashboard HTML page","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/api/v1/files/upload":{"post":{"tags":["Files"],"security":[{"apiKeyAuth":[]}],"summary":"Upload a temporary file","description":"Upload a large file (like a 30MB GLB model) via multipart/form-data. This is highly recommended over sending 40MB base64 JSON payloads which exhaust Cloudflare Worker memory. Returns a robust temporary URL to pass as `glbInput` into pipelines. Files are isolated and expire automatically after a short period.","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"nullable":true,"description":"The binary file payload to upload","format":"binary"}}}}}},"responses":{"201":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"url":{"type":"string","format":"uri","description":"URL to the uploaded temporary file"},"filename":{"type":"string","description":"Original filename"},"size":{"type":"number","description":"File size in bytes"}},"required":["success","url","filename","size"]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/tryon/clothing":{"post":{"tags":["Try-On"],"summary":"Submit clothing try-on request","description":"Generate a virtual try-on image.\n\n## Person Input (choose ONE)\n\n**Option 1: `digitalTwinId` (Recommended)**\nUse a pre-generated digital twin for **consistent face across multiple try-ons**.\nFirst create a twin via `POST /api/v1/digital-twin`, then use the returned ID here.\n\n```json\n{ \"digitalTwinId\": \"abc123...\", \"topGarment\": \"...\" }\n```\n\n**Option 2: `personImages` (Direct mode)**\nSend person image directly to AI. **Warning:** Face may vary between requests since AI regenerates the person each time.\n\n```json\n{ \"personImages\": [\"https://example.com/person.jpg\"], \"topGarment\": \"...\" }\n```\n\n> **Important:** You cannot use both `digitalTwinId` and `personImages` in the same request.\n\n## Garment Types\n\n- `topGarment`: Shirts, blouses, jackets\n- `bottomGarment`: Pants, skirts, shorts\n- `fullBodyGarment`: Dresses, jumpsuits, rompers\n- `shoes`: Optional footwear to add to any outfit\n\n## Garment Image Formats\n\n- Single URL: `\"https://example.com/garment.jpg\"`\n- Array with reference: `[\"packshot.jpg\", \"on-model.jpg\"]` - helps AI understand fit and drape\n\n## Valid Garment Combinations\n\n- Top only, Bottom only, or Top + Bottom\n- Full-body garment (dress/jumpsuit)\n- Any combination above + optional Shoes","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClothingTryOnRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/tryon/shoes":{"post":{"tags":["Try-On"],"summary":"Submit shoe try-on request","description":"Submit a request to generate a virtual shoe try-on image. Returns a job ID for tracking.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShoeTryOnRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/generate":{"post":{"tags":["Generate"],"summary":"Generate or modify images","description":"Submit a request to generate a new image or modify existing images using AI. Returns a job ID for tracking.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/digital-twin":{"post":{"tags":["Digital Twin"],"summary":"Generate digital twin avatar from user photos","description":"Create a photorealistic digital twin avatar that can be reused for unlimited virtual try-ons.\n\n## Photo Mode (Recommended for most apps)\n\nUse this when your users upload regular photos from their phone/camera.\n\n**Request:**\n```json\n{\n  \"faceImage\": \"https://example.com/face-selfie.jpg\",\n  \"bodyPhotoUrl\": \"https://example.com/full-body-photo.jpg\"\n}\n```\n\n**What happens automatically:**\n1. SAM-3D extracts 3D body mesh from the full-body photo (~20s)\n2. Pose transfer applies a standard pose (~15s)\n3. Digital twin is generated with user's face and body shape (~30s)\n\n**Preserve original pose:**\nBy default, a standard pose is applied. To keep the original pose from the photo, set `options.preservePose: true`:\n```json\n{\n  \"faceImage\": \"...\",\n  \"bodyPhotoUrl\": \"...\",\n  \"options\": { \"preservePose\": true }\n}\n```\n\n**Photo requirements:**\n- `faceImage`: Clear face photo (selfie works great)\n- `bodyPhotoUrl`: Full-body photo showing head to feet, person standing\n\n**Choose a pose for try-on:**\nSpecify `poseId` to control the avatar pose:\n- `default`: System default pose (currently girl_pose, configurable)\n- `girl_pose`: Female standing pose\n- `man_pose`: Male standing pose\n- `shoe_girl_pose`: Pose with visible feet (for shoe try-ons)\n- `standing_arms_down`: Arms at sides\n\n**Important:** Each pose creates a **separate cached twin**. If you need the same person in multiple poses, create a twin for each pose. The `digitalTwinId` includes the pose - you cannot change it later.\n\n---\n\n## Direct Mode (Advanced - for custom 3D pipelines)\n\nUse this only if you have your own 3D body scanning/rendering system.\n\n**Request:**\n```json\n{\n  \"faceImage\": \"https://example.com/face.jpg\",\n  \"silhouetteImage\": \"https://example.com/depth-render.png\"\n}\n```\n\nThe `silhouetteImage` must be a pre-rendered depth visualization from a 3D mesh, NOT a regular photo.\n\n---\n\n## Response\n\nReturns a `digitalTwinId` (64-char hex string) that you can save and reuse:\n\n**Use the ID for subsequent try-ons:**\n```json\n// Option 1: POST /api/v1/virtual-fitting\n{ \"digitalTwinId\": \"abc123...\", \"topGarment\": \"...\" }\n\n// Option 2: POST /api/v1/tryon/clothing\n{ \"digitalTwinId\": \"abc123...\", \"topGarment\": \"...\" }\n```\n\n**Benefits:**\n- Face stays consistent across all try-ons\n- Cached for 30 days\n- No need to re-upload photos for each try-on\n- Instant results (skips twin generation)\n\n## Processing Time\n- Photo Mode: ~65 seconds (first time only)\n- Direct Mode: ~30 seconds (first time only)\n- With cached `digitalTwinId`: ~15-25 seconds","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DigitalTwinRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DigitalTwinResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/digital-twin/{id}":{"get":{"tags":["Digital Twin"],"summary":"Check if a digital twin ID is valid","description":"Check if a digital twin ID exists and is available for use in try-on or virtual-fitting requests.\n\nUse this endpoint to verify a cached digital twin is still valid before submitting a job.\nDigital twins expire after 30 days from creation.","parameters":[{"schema":{"type":"string","minLength":1,"description":"The digital twin ID to check","example":"c3721f86f03c1d7035f0728cad2ca97d027781dc7e1722cf92901e10f56007c0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Digital twin status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DigitalTwinStatusResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/generate-3d":{"post":{"tags":["Generate 3D"],"summary":"Generate a 3D model from an image","description":"Submit an image to generate a 3D model. Supports multiple models. Returns a job ID for tracking.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Generate3DRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Generate3DResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/shoe-3d":{"post":{"tags":["Shoe 3D"],"summary":"Generate 3D models of shoes from photos","description":"Generate 3D models of shoes from one or more 2D photos using AI.\n\n## Overview\n\nThis endpoint converts shoe photographs into 3D GLB models.\nYou can run multiple models in parallel to get different results and choose the best one.\n\n## Basic Usage\n\n```json\n{\n  \"images\": [\"https://example.com/shoe.jpg\"]\n}\n```\n\n## Image Recommendations\n\n- **Input:** Clear photo of the shoe with good lighting\n- **Angle:** Show front and side of the shoe for best results\n- **Background:** Plain/solid background works best\n- **Resolution:** Minimum 512x512, recommended 1024x1024\n\n## Image Validation & AI Correction (Default: In-Queue)\n\nBy default, image validation and AI correction run **asynchronously in the queue** after the job is created. This allows for faster API response times.\n\n**Photo requirements checked (during queue processing):**\n- Good, constant lighting (no harsh shadows)\n- Clean, neutral background\n- Only ONE shoe visible\n- Shoe fills the entire frame (no cropping)\n- No reflective lights, people, hands, or watermarks\n\n**Left/Right shoe detection:** The validator detects if the shoe is left or right based on toe direction.\n\n**Multiple images:** When providing multiple photos, the best image is automatically selected for 3D generation.\n\n## AI Image Correction (Default: Enabled)\n\nUser-uploaded photos are automatically transformed into professional packshot images using Gemini 3 Pro before 3D generation. This significantly improves 3D model quality by:\n\n- Generating a clean white background (no shadows or textures)\n- Positioning the shoe at the optimal 3/4 front-side angle\n- Applying studio-quality lighting\n- Preserving exact colors, textures, and brand details\n\nThe corrected image is then validated and used as input for 3D generation.\n\nTo disable correction and use original images directly:\n```json\n{\n  \"images\": [\"https://example.com/shoe.jpg\"],\n  \"options\": {\n    \"correctImage\": false\n  }\n}\n```\n\n## Synchronous Validation Mode\n\nFor use cases where immediate feedback on validation is needed, set `validateInQueue: false`:\n\n```json\n{\n  \"images\": [\"https://example.com/shoe.jpg\"],\n  \"options\": {\n    \"validateInQueue\": false\n  }\n}\n```\n\nWith `validateInQueue: false`:\n- Validation and correction run synchronously during the API request\n- Returns 400 error immediately if validation fails\n- Slower response time due to synchronous processing\n\nWith `validateInQueue: true` (default):\n- The request returns immediately with job ID (no pre-validation)\n- Validation runs asynchronously in the queue\n- If validation fails, the job fails (check job status for details)\n\n## Custom GLB Bypassing (Memory-Safe Uploads)\n\nIf you already possess a custom 3D model and only wish to run our automated Mesh Simplification, AI Texture Enhancement, and WEARFITS AR upload pipelines, you can skip the Generative AI step entirely via the `glbInput` property.\n\nBecause 30MB+ GLB models converted to Base64 JSON strings can cause Cloudflare Worker RAM exhaustion (OOM), we provide a dedicated streaming upload endpoint.\n\n**Step 1: Upload the GLB Model**\n```bash\ncurl -X POST 'https://api.wearfits.com/api/v1/files/upload' \\\n  -H 'X-API-Key: your_api_key' \\\n  -F \"file=@my_model.glb\"\n```\n*(Returns a temporary `url`)*\n\n**Step 2: Run the Pipeline**\n```json\n{\n  \"images\": [\"https://example.com/shoe.jpg\"],\n  \"glbInput\": \"https://api.wearfits.com/files/signed?key=temp%2Fuser-uploads%2F...\",\n  \"options\": {\n    \"enhanceTexture\": true,\n    \"uploadToWearFits\": true\n  }\n}\n```\n\n## WEARFITS AR Integration\n\nEnable `uploadToWearFits` to automatically upload the result to WEARFITS for AR try-on:\n\n```json\n{\n  \"images\": [\"https://example.com/shoe.jpg\"],\n  \"options\": {\n    \"uploadToWearFits\": true,\n    \"sessionId\": \"user-session-id\"\n  }\n}\n```\n\n## Session ID & User Accounts\n\nWhen using `uploadToWearFits: true` in frontend applications, providing the `sessionId` (optional) ensures that the uploaded 3D model is correctly linked to the user's session in WEARFITS. This is important for multi-user environments where objects should belong to specific user accounts.\n\n## Processing Time\n\n- Single model: ~60-180 seconds\n- Multiple models: ~60-180 seconds (parallel execution)\n- WEARFITS upload: +30-60 seconds additional","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Shoe3DRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Shoe3DResponse"}}}},"400":{"description":"Invalid request or shoe image validation failed","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/Shoe3DValidationError"},{"$ref":"#/components/schemas/ErrorResponse"}]}}}}}}},"/api/v1/virtual-fitting":{"post":{"tags":["Virtual Fitting"],"summary":"Complete virtual fitting pipeline (Recommended)","description":"**Recommended endpoint for virtual try-on.** Creates a digital twin and applies garments in a single request, ensuring consistent face across all try-ons.\n\n## Quick Start\n\n```json\n{\n  \"faceImage\": \"https://example.com/face.jpg\",\n  \"silhouetteImage\": \"https://example.com/silhouette.png\",\n  \"topGarment\": \"https://example.com/shirt.jpg\",\n  \"bottomGarment\": \"https://example.com/pants.jpg\"\n}\n```\n\n## Response\n\nReturns a `digitalTwinId` you can save for future try-ons:\n```json\n{\n  \"jobId\": \"...\",\n  \"digitalTwinId\": \"abc123...\",  // Save this!\n  \"status\": \"completed\",\n  \"results\": [{ \"url\": \"...\" }]\n}\n```\n\n## Reusing the Digital Twin\n\nFor subsequent try-ons, pass only the `digitalTwinId` (faster, skips twin generation):\n```json\n{\n  \"digitalTwinId\": \"abc123...\",\n  \"topGarment\": \"https://example.com/different-shirt.jpg\"\n}\n```\n\n---\n\n## Input Modes\n\n**Mode 1: Direct (Recommended)**\nProvide `faceImage` + `silhouetteImage` directly.\n\n**Mode 2: Photo (Auto body extraction)**\nProvide `faceImage` + `photoUrl` + `poseId` - extracts body mesh from photo.\n- Requires full-body photo showing head to feet\n\n**Mode 3: Twin ID (Fastest)**\nProvide only `digitalTwinId` from a previous request - skips twin generation entirely.\n\n**Mode 4: Measurements**\nProvide `faceImage` + `bodyMeasurements` - generates body from sizing data.\n\n---\n\n## Available Poses (Photo Mode only)\n\n- `default`: System default pose\n- `girl_pose`: Female standing pose\n- `man_pose`: Male standing pose\n- `shoe_girl_pose`: Visible feet (for shoe try-ons)\n- `standing_arms_down`: Arms at sides\n\n---\n\n## Caching\n\n- **Digital twins:** Cached 30 days. Same face + silhouette = same `digitalTwinId`\n- **Results:** Cached 7 days. Same twin + garments = instant result\n- Use `options.skipCache: true` to regenerate\n\n## Garment Formats\n\n- Single URL: `\"https://example.com/garment.jpg\"`\n- With reference: `[\"packshot.jpg\", \"on-model.jpg\"]` - helps AI understand fit","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VirtualFittingRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VirtualFittingResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/pose-transfer":{"post":{"tags":["Pose Transfer"],"summary":"Transfer pose from one person to another","description":"Transfer a pose from a reference person (or cached pose) to a source person, generating an image of the source person in the new pose.\n\n## How it works\n\nThis endpoint takes a source person's image and applies a different pose to them while preserving their body shape and facial features.\n\n**Pipeline:**\n1. Extract 3D body mesh from source image using SAM-3D (~20s)\n2. Extract pose from reference image OR use cached pose (~0-20s)\n3. Apply pose transfer to source body mesh (~15s)\n4. Generate photorealistic image of source person in new pose (~30s)\n\n---\n\n## Using a Reference Pose Image\n\nProvide `poseImageUrl` to extract the pose from another person's photo.\n\n```json\n{\n  \"sourceImageUrl\": \"https://example.com/person-a.jpg\",\n  \"poseImageUrl\": \"https://example.com/person-b-pose.jpg\"\n}\n```\n\n**Requirements for pose reference:**\n- Full-body photo showing head to feet\n- Clear pose visible (standing, sitting, dancing, etc.)\n\n**Estimated time:** ~85 seconds\n\n---\n\n## Using a Cached Pose\n\nUse `poseId` for faster processing with pre-defined poses.\n\n```json\n{\n  \"sourceImageUrl\": \"https://example.com/person-a.jpg\",\n  \"poseId\": \"girl_pose\"\n}\n```\n\n**Available poses:**\n- `standing_arms_down`: Natural standing pose with arms relaxed at sides\n- `man_pose`: Cached pose extracted from assets/test/man-pose.jpg\n- `girl_pose`: Cached pose extracted from assets/test/girl-pose.jpg\n\n**Estimated time:** ~65 seconds\n\n---\n\n## Response\n\nReturns:\n- **Image**: Photorealistic image of the source person in the new pose\n- **GLB file** (optional): 3D body mesh with the transferred pose\n- **Visualization** (optional): Mesh visualization PNG\n- **digitalTwinId**: Cache key for reuse in try-on requests\n\nThe `digitalTwinId` can be used with `/api/v1/virtual-fitting` for instant try-ons without regenerating the twin.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoseTransferRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoseTransferResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/digitization/batches":{"post":{"tags":["Digitization"],"summary":"Create a digitization batch","description":"\nSubmit a batch of shoe products for 3D model generation.\nEach product can have multiple images - the best one will be selected automatically.\nUses the same processing logic as single shoe-3d requests (validation, AI correction, WEARFITS upload).\n\n**Key Features:**\n- Each product gets an external ID (productId) for easy tracking\n- Multiple images per product - best one selected automatically\n- AI image correction applied by default\n- Same quality as single shoe-3d requests\n\n**Processing:**\n- Items are processed in parallel with limited concurrency (2 at a time)\n- This queue has lower priority than real-time try-on requests\n- Processing time varies: ~2-5 minutes per shoe\n\n**Batch Limits:**\n- Maximum 500 products per batch\n- Maximum 10 images per product\n- Batches expire after 7 days\n\t","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"products":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"External product ID for tracking (returned in response)","example":"sku-123"},"images":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":10,"description":"Array of image URLs for this product (best selected automatically)","example":["https://example.com/shoe1-front.jpg","https://example.com/shoe1-side.jpg"]}},"required":["images"]},"minItems":1,"maxItems":500,"description":"Array of products to digitize into 3D models"},"options":{"type":"object","properties":{"uploadToWearFits":{"type":"boolean","default":true,"description":"Upload generated 3D models to WEARFITS for AR try-on"},"model":{"type":"string","description":"3D generation model to use (defaults to shoe-3d default model)"},"correctImage":{"type":"boolean","default":true,"description":"Apply AI image correction before 3D generation"},"selectBestModel":{"type":"boolean","default":false,"description":"Run multiple models and select the best result"}}},"webhookUrl":{"type":"string","format":"uri","description":"URL to call when batch completes (all items processed)","example":"https://your-app.com/webhooks/wearfits"}},"required":["products"]}}}},"responses":{"202":{"description":"Batch created and queued for processing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"batch":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","completed","failed","paused"]},"progress":{"type":"object","properties":{"total":{"type":"number"},"completed":{"type":"number"},"failed":{"type":"number"},"pending":{"type":"number"}},"required":["total","completed","failed","pending"]},"createdAt":{"type":"string","format":"date-time"},"statusUrl":{"type":"string","description":"URL to check batch status","example":"/api/v1/digitization/batches/{batchId}"}},"required":["id","status","progress","createdAt","statusUrl"]}},"required":["success","batch"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/digitization/batches/{batchId}":{"get":{"tags":["Digitization"],"summary":"Get batch status","description":"Retrieve the current status and progress of a digitization batch.","parameters":[{"schema":{"type":"string","format":"uuid","description":"Batch ID"},"required":true,"name":"batchId","in":"path"}],"responses":{"200":{"description":"Batch details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"batch":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","completed","failed","paused"]},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"productId":{"type":"string","description":"External product ID provided by the client for tracking"},"images":{"type":"array","items":{"type":"string","format":"uri"},"description":"Array of image URLs for this product (best selected automatically)"},"status":{"type":"string","enum":["pending","queued","processing","completed","failed"]},"jobId":{"type":"string","format":"uuid","description":"Links to the shoe-3d job processing this item"},"glbUrl":{"type":"string"},"viewerUrl":{"type":"string"},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}},"required":["id","images","status"]}},"options":{"type":"object","properties":{"uploadToWearFits":{"type":"boolean","default":true,"description":"Upload generated 3D models to WEARFITS for AR try-on"},"model":{"type":"string","description":"3D generation model to use (defaults to shoe-3d default model)"},"correctImage":{"type":"boolean","default":true,"description":"Apply AI image correction before 3D generation"},"selectBestModel":{"type":"boolean","default":false,"description":"Run multiple models and select the best result"}}},"progress":{"type":"object","properties":{"total":{"type":"number"},"completed":{"type":"number"},"failed":{"type":"number"},"pending":{"type":"number"}},"required":["total","completed","failed","pending"]},"webhookUrl":{"type":"string","format":"uri","description":"URL to call when batch completes (all items processed)","example":"https://your-app.com/webhooks/wearfits"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"apiKeyHash":{"type":"string"}},"required":["id","status","items","options","progress","createdAt","updatedAt"]}},"required":["success","batch"]}}}},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Batch not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/digitization/batches/{batchId}/pause":{"post":{"tags":["Digitization"],"summary":"Pause a batch","description":"Pause processing of a batch. Items already in progress will complete.","parameters":[{"schema":{"type":"string","format":"uuid","description":"Batch ID"},"required":true,"name":"batchId","in":"path"}],"responses":{"200":{"description":"Batch paused","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"status":{"type":"string","enum":["pending","processing","completed","failed","paused"]}},"required":["success","status"]}}}},"400":{"description":"Cannot pause batch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Batch not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/digitization/batches/{batchId}/resume":{"post":{"tags":["Digitization"],"summary":"Resume a paused batch","description":"Resume processing of a paused batch.","parameters":[{"schema":{"type":"string","format":"uuid","description":"Batch ID"},"required":true,"name":"batchId","in":"path"}],"responses":{"200":{"description":"Batch resumed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"status":{"type":"string","enum":["pending","processing","completed","failed","paused"]}},"required":["success","status"]}}}},"400":{"description":"Cannot resume batch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Batch not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/jobs/{jobId}":{"get":{"tags":["Jobs"],"summary":"Get job status","description":"Retrieve the current status and results of a try-on job","parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"jobId","in":"path"}],"responses":{"200":{"description":"Job status retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Jobs"],"summary":"Cancel job","description":"Cancel a pending job. Only jobs in queued or validating status can be cancelled.","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"jobId","in":"path"}],"responses":{"200":{"description":"Job cancelled successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}]}}}},"400":{"description":"Job cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/jobs/{jobId}/trace":{"get":{"tags":["Jobs"],"summary":"Get job execution trace","description":"Retrieve detailed trace log for debugging pipeline failures. Shows timing, inputs, outputs, and retry attempts for each operation. Traces are retained for 7 days after job completion.","parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"jobId","in":"path"}],"responses":{"200":{"description":"Trace log retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraceLogResponse"}}}},"404":{"description":"Job or trace not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/texture-painter/api/enhance":{"post":{"tags":["Texture Painter"],"summary":"AI texture enhancement","description":"Enhance a 3D model texture render using AI and reference packshot images. Accepts base64 data URL images only. Uses OpenRouter (Gemini) to generate an enhanced texture that maintains the original silhouette.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextureEnhanceRequest"}}}},"responses":{"200":{"description":"Enhanced texture image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextureEnhanceResponse"}}}}}}},"/texture-painter/api/split-materials":{"post":{"summary":"Split GLB materials by UV quadrants (Async)","description":"Starts an async job to divide a GLB mesh primitives into quadrants based on GenAI semantic mapping.","tags":["Texture Painter"],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"glb":{"description":"The GLB file to split materials for","format":"binary"},"textureType":{"type":"string","enum":["baseColorTexture","metallicRoughnessTexture","normalTexture","emissiveTexture","occlusionTexture","none"],"description":"The texture channel to use for SAM2 segmentation / K-Means clustering (if none or absent, uses 4 quadrants)"},"method":{"type":"string","enum":["multiview_ai"],"default":"multiview_ai","description":"The algorithm method to use for splitting materials."}},"required":["glb"]}}}},"responses":{"202":{"description":"Job Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"jobId":{"type":"string"}},"required":["success","jobId"]}}}},"400":{"description":"Bad Request"},"500":{"description":"Internal Server Error"}}}},"/texture-painter/api/status/{jobId}":{"get":{"summary":"Get split materials job status","tags":["Texture Painter"],"parameters":[{"schema":{"type":"string"},"required":true,"name":"jobId","in":"path"}],"responses":{"200":{"description":"Job Status","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["processing","completed","failed"]},"resultUrl":{"type":"string"},"error":{"type":"string"}},"required":["status"]}}}}}}}}}