Skip to main content

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.

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.
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.
POST /tryon/v1/try-on
{ "productId": "shirt-42",
  "customerImageId": "..." }
The response now includes productExternalId — useful for one-shot calls so you can re-use the same product later (server-generated IDs start with _anon_).
Sending the old productId field returns 400 Bad Request. There’s no alias.

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
PATCH /tryon/v1/products/shirt-42
{ "title": "Red tee — restocked" }
v1
POST /tryon/v1/products
{ "externalId": "shirt-42", "title": "Red tee — restocked" }
Creating a brand-new product still requires at least one image. 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
GET /tryon/v1/generations?limit=50&status=PENDING
v1
GET /tryon/v1/generations/<id>
If you were using the list endpoint to recover lost generation ids, switch to storing them as part of your own write — the create response is the only place an id is now revealed.

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
{ "imageUrls": ["https://cdn.example/front.jpg", "https://cdn.example/back.jpg"] }
v1 — URL only
{ "images": [
    { "url": "https://cdn.example/front.jpg" },
    { "url": "https://cdn.example/back.jpg" }
  ] }
v1 — multipart (mix URL + bytes)
Content-Type: multipart/form-data

data: { "images": [
  { "url": "https://cdn.example/front.jpg" },
  { "fileKey": "back" }
]}
back: <binary>

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 pathDefault lifetimeOverride?
POST /tryon/v1/productsKept forever (null)Yes — pass validForDays
POST /try-on inline + externalId15 days from last useYes — pass validForDays
POST /try-on one-shot7 days from last useNo — fixed
Lifetimes refresh on every generation. 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.idrecommended, image id from a prior /images/upload. The only path with control over cropping (set crop=false on upload).
  • customer.url — server downloads on every call. Always 4:5-cropped.
  • customer.fileKey — multipart file in the same /try-on request. Always 4:5-cropped.
Alpha
{ "product": { ... }, "customerImageId": "..." }
v1
{ "product": { ... }, "customer": { "id": "..." } }
The old nested 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)
{ "error": "Product not found", "code": "INVALID_PRODUCT" }
Now (v1)
{ "code": "PRODUCT_NOT_FOUND", "message": "Product 'shirt-42' not found.", "status": 404 }
The legacy 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 productId with product.externalId on every /try-on call.
  • Replace customerImageId (top-level) and customer: { imageId } (nested) with customer: { id }.
  • Drop any PATCH /products/:externalId calls; use POST /products with partial fields.
  • Replace GET /generations polling with per-id GET /generations/:id. Track each generation id from the /try-on response.
  • Update error handling to read the new { code, message, status } shape — switch on code, not message strings. Replace any INVALID_PRODUCT checks with PRODUCT_NOT_FOUND / VALIDATION_FAILED as appropriate.
  • Drop any code reading productId from POST /products responses. Reference products by externalId instead.
  • Drop any code reading resultImageKey from GET /generations/:id. Use resultImageUrl only.
  • Image fields on product responses are now { sourceUrl, order } — the id/storageKey/productId columns are no longer exposed.
  • (Optional) Replace imageUrls with images: [{ 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 externalId entirely — server returns productExternalId so you can ref the row if you want.
  • Add productExternalId handling to your response parsers if you want to capture anonymous IDs.

Need help?

support@genlook.app — we’ll happily review your integration’s diff and flag anything that needs updating.