FitInView Try-On API

Generate photorealistic virtual try-ons via REST. Submit a person photo + garment images, get back a composed result image.

Built for scrapers, e-commerce backends, and LLM agents.

Get an API key → · openapi.json

Base URL

https://api.fitinview.com

All endpoints are also reachable on https://fitinview.com (legacy). Prefer api.fitinview.com for production.

Authentication

Bearer token in the Authorization header. Get a key from /developers.

Authorization: Bearer fiv_live_…

All POST requests require Content-Type: application/json. GET requests need only the bearer token.

Pricing

Important: API credits live in a separate pool from FitInView's web/app credits. Packs bought on this developer page only work for API calls — and vice versa, credits earned in the website don't apply here.

Credits are deducted atomically before generation. Cost depends on the requested output resolution:

Output sizeApprox. resolutionCredits per try-on
1K~1024px (default)8
2K~2048px12
4K~4096px20

The "try-on counts" shown below assume 1K — divide by 1.5× for 2K, by 2.5× for 4K.

Rate limits

Test mode (free, no charge)

Keys with the fiv_test_* prefix don't call the model and don't burn credits. They return a stock image and echo your metadata + seed. Use them for CI, smoke tests, and integration scaffolding.

Mint a test key from /developers. You get one test key per account in addition to your live keys.

Field aliases (lenient parsing)

To stay friendly to LLM-generated requests, the API accepts several common aliases. Canonical names are still preferred:

CanonicalAlso acceptedNotes
output_sizeresolution, sizeValues are case-insensitive: 1k = 1K
wait: true/falsemode: "sync" / mode: "async"Boolean wins if both sent
person_urlperson, model_url, input_urlUsed only if canonical is empty
garment_urlsgarments, clothing, itemsUsed only if canonical is empty

Result images are always returned as PNG (signed S3 URL).

Endpoints

POST /api/v1/tryon

Submit a try-on. Sync by default — returns the result inline within 90 seconds.

Request body

FieldTypeRequiredDescription
person_urlstringperson required*HTTPS URL of person photo. Provide either person_url or person_image_b64 (not both — if both are sent, person_image_b64 wins).
person_image_b64stringperson required*Base64-encoded person photo (raw bytes, no data-URL prefix). Either this or person_url must be set.
garment_urlsstring[]garment required*0-3 HTTPS garment image URLs. Combined with garment_images_b64 the total must be 1-3.
garment_images_b64string[]garment required*0-3 base64 garment images. URLs and base64 entries can be mixed in the same request.
output_sizestringno"1K" (default, 8 credits, ~1024px), "2K" (12 credits, ~2048px), or "4K" (20 credits, ~4096px).
promptstringnoCustom instruction, max 1000 chars. Appended after the canonical "try on these garments" text — does not replace it. The system prompt + identity-preservation rules apply automatically and cannot be overridden.
style_hintstringnoShort style direction, max 500 chars. Ignored when prompt is set. Kept for backward compatibility.
waitbooleannotrue (default) blocks up to 90s and returns the result inline. false returns immediately with {job_id, status: "running"} — you then poll for completion.
metadataobjectnoUp to 8 key/value pairs (256 chars each). Returned unchanged on the response and on poll. Use this to correlate jobs to your own user/order IDs.
seedintegernoOptional seed (0 to 2³¹-1). Echoed in the response.
negative_promptstringnoWhat NOT to include (max 500 chars). Appended as "avoid: ..." direction.
webhook_urlstring (HTTPS URL)noPOST'd a signed event when the job terminates. See "Webhooks" below.

* At least one of the person fields and at least one of the garment fields must be set.

Example (cURL)

curl -X POST https://api.fitinview.com/api/v1/tryon \
  -H "Authorization: Bearer fiv_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "person_url": "https://example.com/me.jpg",
    "garment_urls": ["https://example.com/shirt.jpg"],
    "wait": true,
    "output_size": "1K"
  }'

Example (Python)

import httpx
r = httpx.post(
    "https://api.fitinview.com/api/v1/tryon",
    headers={"Authorization": "Bearer fiv_live_…"},
    json={
        "person_url": "https://example.com/me.jpg",
        "garment_urls": ["https://example.com/shirt.jpg"],
        "wait": True,
    },
    timeout=120,
)
print(r.json()["result_url"])

Response — sync success (HTTP 200)

{
  "job_id": "job_abc123…",
  "status": "succeeded",
  "result_url": "https://hel1.your-objectstorage.com/…?Expires=…",
  "cost_credits": 8,
  "credits_remaining": 142
}

Response — sync exceeded 90s (HTTP 200)

{
  "job_id": "job_abc123…",
  "status": "running",
  "message": "Job exceeded 90s sync limit; poll GET /api/v1/tryon/{job_id}"
}

Response — async submission, wait=false (HTTP 200)

{
  "job_id": "job_abc123…",
  "status": "running"
}

Response — sync failure (HTTP 200, with credits NOT refunded — Gemini was called)

{
  "job_id": "job_abc123…",
  "status": "failed",
  "error_code": "generation_failed",
  "error_message": "content_blocked"
}

result_url is a presigned S3 URL valid for 24 hours. Download and store the image yourself for longer retention.

GET /api/v1/tryon

List recent jobs (newest first), cursor-paginated.

Query parameters:

{
  "jobs": [ {...TryOnJobStatus...}, ... ],
  "next_cursor": "job_xxx"   // null when no more pages
}

GET /api/v1/tryon/{job_id}

Poll a job. Use this after a wait=false submission, or when sync mode returned status: "running" at 90s.

job_id is the value returned from the submit call (always starts with job_).

Response — running

{
  "job_id": "job_abc123…",
  "status": "running",
  "result_url": null,
  "error_code": null,
  "error_message": null,
  "cost_credits": 8,
  "duration_ms": null,
  "created_at": "2026-04-29T16:44:52Z",
  "completed_at": null
}

Response — succeeded

{
  "job_id": "job_abc123…",
  "status": "succeeded",
  "result_url": "https://hel1.your-objectstorage.com/…?Expires=…",
  "error_code": null,
  "error_message": null,
  "cost_credits": 8,
  "duration_ms": 18452,
  "created_at": "2026-04-29T16:44:52Z",
  "completed_at": "2026-04-29T16:45:11Z"
}

Response — failed (credits NOT refunded — Gemini was called)

{
  "job_id": "job_abc123…",
  "status": "failed",
  "result_url": null,
  "error_code": "generation_failed",
  "error_message": "content_blocked",
  "cost_credits": 8,
  "duration_ms": 4210,
  "created_at": "2026-04-29T16:44:52Z",
  "completed_at": "2026-04-29T16:44:56Z"
}

All timestamps are ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ).

GET /api/v1/account

Inspect your key's balance and limits.

{
  "name": "Production",          // The label you set when minting the key
  "credits_remaining": 142,      // Current credit balance
  "rate_limit_per_min": 20,      // Requests/minute allowed
  "total_calls": 17,             // Lifetime calls on this key
  "tryon_cost_credits": {        // Cost per try-on by output size
    "1K": 8, "2K": 12, "4K": 20
  }
}

Idempotency

Pass an Idempotency-Key header (any string up to 200 chars) on POST /api/v1/tryon to safely retry on network errors:

Idempotency-Key: ord_42-attempt-1

Webhooks

Set webhook_url on submit to skip polling:

{
  "person_url": "...",
  "garment_urls": ["..."],
  "wait": false,
  "webhook_url": "https://yourapp.com/hooks/fitinview"
}

When the job reaches a terminal state (succeeded or failed), we POST to your URL with these headers:

HeaderValue
X-FitInView-Signaturesha256=<hex> — HMAC-SHA256 of the raw body, keyed by your webhook_secret (shown once when you mint the key, prefix whsec_)
X-FitInView-Eventtryon.completed
X-FitInView-Job-IdThe job id (matches body)

Body:

{
  "event": "tryon.completed",
  "job_id": "job_abc...",
  "status": "succeeded",
  "result_url": "https://...png?Expires=...",
  "error_code": null,
  "error_message": null,
  "duration_ms": 18452,
  "completed_at": "2026-04-29T16:45:11Z"
}

Verify the signature in Python:

import hmac, hashlib
secret = "whsec_..."  # shown once when you minted the key
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, request.headers["X-FitInView-Signature"])

Delivery: best-effort with up to 3 retries (1s, 4s, 16s backoff). Respond with any 2xx within 10 seconds. The signed URL in the body is valid for 24 hours.

Errors

All errors return JSON with the same shape:

{
  "detail": "Insufficient credits"
}

For try-on submissions that fail after reaching the model, the failure is reported as a 200 with status: "failed", error_code, and error_message (see above).

StatusMeaningCredits charged?
400Bad request (missing person/garment, image too large, invalid URL, image fetch failed)No
401Missing or invalid API keyNo
402Insufficient creditsNo
403Key disabledNo
404Job not foundNo
429Rate limit exceededNo
500Server error before model callRefunded
200 + status:"failed"Model rejected, content blocked, storage failedRefunded

Agentic commerce (Stripe Shared Payment Tokens)

AI agents acting on behalf of a user can purchase credit packs without exposing card credentials, using Stripe Shared Payment Tokens. The agent must already hold both:

Endpoint: POST /api/v1/credits/purchase

curl -X POST https://api.fitinview.com/api/v1/credits/purchase \
  -H "Authorization: Bearer fiv_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "pack_slug": "starter",
    "shared_payment_granted_token": "spt_..."
  }'

Response (status=succeeded):

{
  "payment_intent_id": "pi_...",
  "status": "succeeded",
  "pack_slug": "starter",
  "credits_added": 1200,
  "credits_remaining": 1247,
  "next_action": null
}

Other PaymentIntent statuses (processing, requires_action) return immediately; credits are granted via the payment_intent.succeeded webhook once the payment finalizes. The endpoint is idempotent on (api_key_id, SPT) so retries are safe. Test keys (fiv_test_*) short-circuit to a fake succeeded response without touching Stripe and without granting real credits.

Discoverability: GET /api/discovery exposes capabilities.agentic_commerce for crawler/agent auto-discovery.

Notes & tips

Privacy & data retention

Content policy

The API will refuse (returning status: "failed" with error_code: "generation_failed" + error_message: "content_blocked") when the input or requested output appears to involve:

Repeated content_blocked events on a key may trigger automatic suspension. Credits are not refunded on content_blocked (the model was invoked).

Versioning & changelog

FitInView API v1 · api@fitinview.com