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
https://api.fitinview.com
All endpoints are also reachable on https://fitinview.com (legacy). Prefer api.fitinview.com for production.
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.
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 size | Approx. resolution | Credits per try-on |
|---|---|---|
1K | ~1024px (default) | 8 |
2K | ~2048px | 12 |
4K | ~4096px | 20 |
The "try-on counts" shown below assume 1K — divide by 1.5× for 2K, by 2.5× for 4K.
X-RateLimit-Limit — your per-minute quotaX-RateLimit-Remaining — calls left in the current windowX-RateLimit-Reset — unix timestamp when the oldest counted call ages out429 you also get Retry-After (seconds). Use these to pace requests instead of reacting after a 429.wait=true) blocks up to 90 seconds. If generation isn't done by then, the response returns {job_id, status: "running", message} — poll GET /api/v1/tryon/{job_id} for the result.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.
To stay friendly to LLM-generated requests, the API accepts several common aliases. Canonical names are still preferred:
| Canonical | Also accepted | Notes |
|---|---|---|
output_size | resolution, size | Values are case-insensitive: 1k = 1K |
wait: true/false | mode: "sync" / mode: "async" | Boolean wins if both sent |
person_url | person, model_url, input_url | Used only if canonical is empty |
garment_urls | garments, clothing, items | Used only if canonical is empty |
Result images are always returned as PNG (signed S3 URL).
/api/v1/tryonSubmit a try-on. Sync by default — returns the result inline within 90 seconds.
| Field | Type | Required | Description |
|---|---|---|---|
person_url | string | person 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_b64 | string | person required* | Base64-encoded person photo (raw bytes, no data-URL prefix). Either this or person_url must be set. |
garment_urls | string[] | garment required* | 0-3 HTTPS garment image URLs. Combined with garment_images_b64 the total must be 1-3. |
garment_images_b64 | string[] | garment required* | 0-3 base64 garment images. URLs and base64 entries can be mixed in the same request. |
output_size | string | no | "1K" (default, 8 credits, ~1024px), "2K" (12 credits, ~2048px), or "4K" (20 credits, ~4096px). |
prompt | string | no | Custom 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_hint | string | no | Short style direction, max 500 chars. Ignored when prompt is set. Kept for backward compatibility. |
wait | boolean | no | true (default) blocks up to 90s and returns the result inline. false returns immediately with {job_id, status: "running"} — you then poll for completion. |
metadata | object | no | Up 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. |
seed | integer | no | Optional seed (0 to 2³¹-1). Echoed in the response. |
negative_prompt | string | no | What NOT to include (max 500 chars). Appended as "avoid: ..." direction. |
webhook_url | string (HTTPS URL) | no | POST'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.
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"
}'
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"])
{
"job_id": "job_abc123…",
"status": "succeeded",
"result_url": "https://hel1.your-objectstorage.com/…?Expires=…",
"cost_credits": 8,
"credits_remaining": 142
}
{
"job_id": "job_abc123…",
"status": "running",
"message": "Job exceeded 90s sync limit; poll GET /api/v1/tryon/{job_id}"
}
wait=false (HTTP 200){
"job_id": "job_abc123…",
"status": "running"
}
{
"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.
/api/v1/tryonList recent jobs (newest first), cursor-paginated.
Query parameters:
limit (1-100, default 20)cursor — pass the next_cursor from the previous pagestatus_filter — optional, one of queued | running | succeeded | failed{
"jobs": [ {...TryOnJobStatus...}, ... ],
"next_cursor": "job_xxx" // null when no more pages
}
/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_).
{
"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
}
{
"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"
}
{
"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).
/api/v1/accountInspect 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
}
}
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
409 Conflict.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:
| Header | Value |
|---|---|
X-FitInView-Signature | sha256=<hex> — HMAC-SHA256 of the raw body, keyed by your webhook_secret (shown once when you mint the key, prefix whsec_) |
X-FitInView-Event | tryon.completed |
X-FitInView-Job-Id | The 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.
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).
| Status | Meaning | Credits charged? |
|---|---|---|
| 400 | Bad request (missing person/garment, image too large, invalid URL, image fetch failed) | No |
| 401 | Missing or invalid API key | No |
| 402 | Insufficient credits | No |
| 403 | Key disabled | No |
| 404 | Job not found | No |
| 429 | Rate limit exceeded | No |
| 500 | Server error before model call | Refunded |
200 + status:"failed" | Model rejected, content blocked, storage failed | Refunded |
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:
spt_...) granted to it by the user, scoped to a merchant + amount + expiryEndpoint: 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.
1K ≈ 1024px (8 cr), 2K ≈ 2048px (12 cr), 4K ≈ 4096px (20 cr).job_id to delete a result before the auto-purge window.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).
/api/v1 path is stable: any breaking schema change ships under /api/v2 with at least 90 days notice via this docs page.FitInView API v1 · api@fitinview.com