The Try-On API graduated from alpha to v1.0.0. The surface was reshaped along the way — this page walks you through what changed and how to update. For the full release notes see the Changelog.Documentation Index
Fetch the complete documentation index at: https://docs.genlook.app/docs/llms.txt
Use this file to discover all available pages before exploring further.
Three small breakings, several quality-of-life additions, and a new lifetime model for products. Most integrations
migrate in 10 minutes.
At a glance
| Area | Was (alpha) | Now (v1) | | ----------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | |POST /try-on body shape | { productId, customerImageId } | { product: { externalId or …inline… }, customer: { id or url or fileKey } } |
| PATCH /products/:externalId | Redundant alias for upsert | Removed. Use POST /products with the fields you want to change (partial updates supported). |
| Product images on upsert | imageUrls: string[] only | images: [{ url | fileKey }]. Mix URLs with multipart uploads. imageUrls still accepted as a deprecated alias. |
| Customer image on /try-on | customerImageId only | One customer object with exactly one of id, url, or fileKey. |
| Customer-image crop | Always 4:5 person-aware | crop=true | falseform field on/images/upload(defaulttrue). |
| Products & lifetime | No expiry — products lived forever | Inline-created products expire 15 days after last use; products created via POST /products are kept forever by default. |
| One-shot products | Did not exist | Call /try-on without an externalId — the response includes a productExternalId you can re-use. |
| /try-on response | { generationId, status } | + productExternalId (the product’s external ID, including any server-generated one). |
1. POST /try-on — single product field
The whole body collapses to one product field that covers three use cases.
productExternalId — useful for one-shot calls so you can re-use the same product later (server-generated IDs start with _anon_).
2. PATCH /products/:externalId removed
The PATCH route now returns 404. Use POST /tryon/v1/products with just the fields you want to change — the endpoint now supports partial updates on existing products.
Alpha
v1
title and description are optional (they improve the AI’s category classification when provided). validForDays is preserve-on-omit on updates.
3. GET /tryon/v1/generations (paginated list) removed
The public API used to expose a paginated list of every generation on the account. It now exposes only status by id: capture the generationId from each POST /try-on response and poll GET /generations/:id when you need the result.
Alpha
v1
4. images replaces imageUrls
imageUrls: string[] still works in v1 but is deprecated; it will be removed in v2. The new shape lets URLs and multipart uploads mix in the same payload.
Alpha
v1 — URL only
v1 — multipart (mix URL + bytes)
Behaviour changes (non-breaking, but visible)
These don’t break callers — but the visible behaviour shifts. Worth a skim.Product lifetimes by creation path
| Creation path | Default lifetime | Override? |
|---|---|---|
POST /tryon/v1/products | Kept forever (null) | Yes — pass validForDays |
POST /try-on inline + externalId | 15 days from last use | Yes — pass validForDays |
POST /try-on one-shot | 7 days from last use | No — fixed |
validForDays is preserved when you don’t supply it on updates. A product first created inline (15d) can be promoted to “kept forever” later by calling POST /products with validForDays: null.
One-shot products are reachable by ID
Every/try-on response now includes productExternalId. For one-shots, this is a server-generated ID (starts with _anon_) that you can re-use on subsequent calls to reference the same product. One-shot products don’t appear in GET /products (they’re not part of your catalog), but GET /products/:externalId and DELETE /products/:externalId accept their IDs. POST /products with an _anon_* ID returns 409 Conflict — that prefix is reserved for server-generated IDs.
Customer image — single customer object
The top-level customerImageId shorthand is gone. POST /try-on now takes one customer object with exactly one of three fields:
customer.id— recommended, image id from a prior/images/upload. The only path with control over cropping (setcrop=falseon upload).customer.url— server downloads on every call. Always 4:5-cropped.customer.fileKey— multipart file in the same/try-onrequest. Always 4:5-cropped.
Alpha
v1
customer: { imageId } form is also gone — the field is now id. Migration is a two-line search-replace.
Crop flag on customer-image upload
POST /tryon/v1/images/upload reads a crop form field. Default true matches the old always-crop behaviour. Pass crop=false to keep the original framing (studio shots, model previews, anything where the framing matters).
POST /try-on accepts multipart
When the inline product references uploaded files via fileKey, the request is multipart/form-data with a JSON data field + named file fields. Pure-JSON URL-only callers are unaffected.
Partial updates on POST /tryon/v1/products
Hitting the endpoint with an existing externalId and only the fields you want to change merges over stored values. Sending a different images array still replaces the full list — there’s no per-image patching.
Concurrent identical try-on calls are safe
Two parallel/try-on calls with the same inline content resolve to the same product — no duplicate rows, no race conditions.
Error responses are now { code, message, status }
Every /tryon/v1/* error returns the same JSON shape, with a stable code from a documented enum. The previous responses mixed several formats — bare "message" strings, ad-hoc { error, code } objects, raw Zod issues. Branch on code going forward; treat message as polished text. Full catalog at Errors.
Was (alpha)
Now (v1)
INVALID_PRODUCT code is gone — it folded into PRODUCT_NOT_FOUND (ref to a missing externalId) and VALIDATION_FAILED (Zod rejection of the inline payload). Validation errors also carry a details array with the per-field issues.
Migration checklist
- Replace
productIdwithproduct.externalIdon every/try-oncall. - Replace
customerImageId(top-level) andcustomer: { imageId }(nested) withcustomer: { id }. - Drop any
PATCH /products/:externalIdcalls; usePOST /productswith partial fields. - Replace
GET /generationspolling with per-idGET /generations/:id. Track each generation id from the/try-onresponse. - Update error handling to read the new
{ code, message, status }shape — switch oncode, not message strings. Replace anyINVALID_PRODUCTchecks withPRODUCT_NOT_FOUND/VALIDATION_FAILEDas appropriate. - Drop any code reading
productIdfromPOST /productsresponses. Reference products byexternalIdinstead. - Drop any code reading
resultImageKeyfromGET /generations/:id. UseresultImageUrlonly. - Image fields on product responses are now
{ sourceUrl, order }— theid/storageKey/productIdcolumns are no longer exposed. - (Optional) Replace
imageUrlswithimages: [{ url }, ...]. Deprecation window is one major. - (Optional) If you’re shipping byte uploads on every call, switch to upserting once and referencing by
externalId— the new TTL refresh keeps the row alive as long as you keep using it. - (Optional) For one-shot use cases, drop
externalIdentirely — server returnsproductExternalIdso you can ref the row if you want. - Add
productExternalIdhandling to your response parsers if you want to capture anonymous IDs.

