← Back to Docs

Outfit Try-On API

POST /v1/virtual-tryon/outfit — supply a top and a bottom garment in one call, receive one composite result. No manual poll-and-resubmit loop needed.
Live as of 2026-07-13. The endpoint is in production and hitting auth correctly. Poll results with the existing GET /v1/virtual-tryon/jobs/{job_id} endpoint.

Overview

The standard POST /v1/virtual-tryon endpoint accepts one garment at a time. If you want to dress a model in a coordinated outfit — a shirt and trousers — you previously had to run two separate calls and thread the intermediate result manually.

The /outfit endpoint removes that complexity. Send the person photo, the top garment, and the bottom garment in a single request. The API runs two sequential VTON passes internally and returns one composite result.

1
Bottom-first pass — the lowerbody garment is applied to the original person photo. This produces an intermediate composite with the correct trouser/skirt fit on the person's legs and hips.
2
Top pass — the upperbody garment is applied to the stage-1 output (not the original photo). This ensures the collar/hem boundary of the top aligns correctly with the already-fitted bottom, eliminating cloth bleeding between garments.
3
Result — the final composite URL is written to the parent job row. Poll /v1/virtual-tryon/jobs/{job_id} as usual. No extra endpoints to call.
Cloth-bleeding prevention. The bottom-first order is intentional and not configurable. Applying the top first leaves the waistband/hem region of the bottom garment undefined, causing the model to bleed fabric colour across the boundary. Bottom-first resolves this by establishing the trouser silhouette before the top is rendered.

Endpoint

POST https://api.pixelapi.dev/v1/virtual-tryon/outfit
Authorization: Bearer <your_api_key>
Content-Type: application/json

Request body

FieldTypeRequiredDescription
person_imagestring (base64)requiredBase64-encoded JPEG, PNG, or WebP of the person / model. Must show full or half-body (not a headshot). Max 10 MB decoded.
upperbody_garmentstring (base64)requiredBase64-encoded garment photo of the top (shirt, jacket, blouse, hoodie). Flat-lay or product shot on a plain background. Max 10 MB decoded.
lowerbody_garmentstring (base64)requiredBase64-encoded garment photo of the bottom (trousers, jeans, skirt). Flat-lay or product shot. Full-length pants produce the best results; shorts may render with limited leg coverage.
n_stepsintegeroptionalDiffusion steps per stage (1–50, default 40, minimum enforced to 40 for quality).
image_scalefloatoptionalGuidance scale (0.1–10.0, default 2.5, minimum enforced to 2.5).
webhook_urlstringoptionalHTTPS URL to POST the completed result to. Must be a valid HTTPS endpoint.

Response (202 Accepted)

{
  "job_id": "1bf21d2d-7432-4930-a6d3-e941d2157d80",
  "status": "processing",
  "eta_seconds": 90,
  "poll_url": "/v1/virtual-tryon/jobs/1bf21d2d-7432-4930-a6d3-e941d2157d80",
  "credits_used": 22,
  "processing_stages": 2,
  "stage_order": "lowerbody first, then upperbody layered over result"
}
FieldDescription
job_idUUID of the parent job. Use this in the poll endpoint.
status"processing" — the chain is running; poll for completion.
eta_secondsEstimated total time for both stages (~90s under normal load).
poll_urlRelative path to GET /v1/virtual-tryon/jobs/{job_id}.
credits_used22 credits on standard plans (2× the single-garment cost of 11cr). 500 credits on SCALE plans (2× 250cr).
processing_stagesAlways 2.
stage_orderInformational string confirming processing order.

Polling for results

Use the standard job endpoint — no changes needed:

GET https://api.pixelapi.dev/v1/virtual-tryon/jobs/{job_id}
Authorization: Bearer <your_api_key>

When status === "completed", the output_url field contains the final composite image URL (or result_image_b64 for inline base64). On failure, credits are auto-refunded.

Pricing and timing

PlanCredits per outfit jobApprox. time
Free / Starter / Pro22 credits (2× 11cr)~90s (two GPU passes)
SCALE500 credits (2× 250cr)~30–45s (managed fast lane)
If stage 1 (lower) fails, both stages are refunded. If stage 2 (upper) fails, stage 2 credits are refunded and stage 1's lower-only result is returned as the final output — you are never charged for a completely empty result.

Preflight gates applied

All the same quality and safety gates from the standard endpoint are applied to both garments and to the person photo:

No credits are charged if any gate rejects the input.

Known limitation

Shorts as lowerbody: The underlying VTON model (VITON-HD-trained) renders shorts with poor quality — skin patches appear on calves because the training set consisted of full-length pants only. This limitation also exists in the single-garment lowerbody path and is unrelated to the outfit endpoint. Full-length trousers, jeans, and skirts render correctly.

Full example (cURL)

PERSON=$(base64 -w0 person.jpg)
TOP=$(base64 -w0 shirt.jpg)
BOTTOM=$(base64 -w0 trousers.jpg)

curl -s -X POST https://api.pixelapi.dev/v1/virtual-tryon/outfit \
  -H "Authorization: Bearer $PIXELAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "person_image":       "'"$PERSON"'",
    "upperbody_garment":  "'"$TOP"'",
    "lowerbody_garment":  "'"$BOTTOM"'"
  }' | jq .job_id

Full example (Python)

import requests, base64, time

def b64(path):
    return base64.b64encode(open(path,'rb').read()).decode()

resp = requests.post(
    "https://api.pixelapi.dev/v1/virtual-tryon/outfit",
    headers={"Authorization": "Bearer " + API_KEY},
    json={
        "person_image":      b64("person.jpg"),
        "upperbody_garment": b64("shirt.jpg"),
        "lowerbody_garment": b64("trousers.jpg"),
    },
)
job_id = resp.json()["job_id"]

# Poll until done
while True:
    time.sleep(5)
    r = requests.get(
        f"https://api.pixelapi.dev/v1/virtual-tryon/jobs/{job_id}",
        headers={"Authorization": "Bearer " + API_KEY},
    ).json()
    if r["status"] == "completed":
        print("Result:", r["output_url"])
        break
    if r["status"] == "failed":
        print("Failed:", r["error_message"])
        break
    print(f"  {r['status']} — ETA {r.get('eta_seconds','?')}s")

Lensora / mobile integration

The /outfit endpoint is a plain JSON REST call, identical in authentication and polling to the existing single-garment endpoint. From the Lensora Android app:

  1. Accept three image inputs from the user in the virtual try-on screen: person photo, top garment, bottom garment.
  2. Base64-encode each image before sending (same as the existing single-garment flow).
  3. POST to /v1/virtual-tryon/outfit with Authorization: Bearer <user_token>.
  4. Poll /v1/virtual-tryon/jobs/{job_id} every 5 seconds. ETA is ~90s.
  5. Display the result when status === "completed". Show the user a cost notice of 22 credits before submitting (2× the single-garment cost).
No new authentication scopes are needed. The endpoint uses the same bearer token issued by /v1/auth/token.
Try in the web app Back to all docs Developer portal

Last updated: 2026-07-13 · [email protected]