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.

Every /tryon/v1/* endpoint returns errors in the same JSON shape. Branch on code — it’s the stable contract. Treat message as human-readable text that may be polished between releases.
Error shape
{
  "code": "PRODUCT_NOT_FOUND",
  "message": "Product 'shirt-42' not found.",
  "status": 404
}
For validation failures (Zod rejections of the request body) the response also includes a details array with one entry per offending field:
Validation shape
{
  "code": "VALIDATION_FAILED",
  "message": "Request body failed validation. See `details` for per-field errors.",
  "status": 400,
  "details": [
    { "path": "product.images.0.url", "message": "Required" },
    { "path": "validForDays",        "message": "Expected number, received string" }
  ]
}
The HTTP status in the response always matches the status field. You can read either — both are part of the contract.

Codes

Codes are grouped by what triggered them. Branching on the code is enough — the same code always means the same thing across endpoints.

Auth

CodeStatusWhen you’ll see it
MISSING_API_KEY401x-api-key header is missing.
INVALID_API_KEY401The key was rejected — wrong, revoked, or inactive account.

Authorization

CodeStatusWhen you’ll see it
FORBIDDEN403Requested resource (product, generation) belongs to a different account.

Request validation (400)

CodeWhen you’ll see it
VALIDATION_FAILEDZod rejected the request body. details[] lists { path, message } for every offending field.
EXTERNAL_ID_REQUIREDPOST /products without externalId.
PRODUCT_IMAGES_REQUIREDCreating or one-shotting a product without any image.
CUSTOMER_IMAGE_REQUIREDPOST /try-on without a customer object.
FILE_REQUIREDPOST /images/upload called with no file field.
MULTIPART_FILE_NOT_FOUNDA fileKey referenced from data doesn’t appear in the multipart payload.
INVALID_IMAGE_INPUTAn images[] entry has neither url nor fileKey.
INVALID_IMAGEUploaded bytes are too small to be an image, or otherwise malformed before format detection.
UNSUPPORTED_IMAGE_TYPEThe uploaded file isn’t JPEG, PNG, WebP, or HEIC.
CUSTOMER_IMAGE_FETCH_FAILEDcustomer: { url } could not be downloaded — DNS failed, host unreachable, or the URL returned a non-2xx status.
PRODUCT_IMAGE_FETCH_FAILEDA product image URL could not be downloaded during generation processing. Surfaces on GET /generations/:id as errorCode once the generation transitions to FAILED.

Payload size

CodeStatusWhen you’ll see it
FILE_TOO_LARGE413Any single image attachment exceeds 10 MB (customer image or product image).

Billing

CodeStatusWhen you’ll see it
INSUFFICIENT_CREDITS402Account has 0 credits remaining. Top up and retry.
QUOTA_EXCEEDED402The account-level quota check rejected the call. Back off and retry — these are usually transient.

Rate limiting

CodeStatusWhen you’ll see it
RATE_LIMITED429Per-account request budget exceeded. Back off and retry. Burst limits are higher than steady-state — see your dashboard.

Not found

CodeStatusWhen you’ll see it
PRODUCT_NOT_FOUND404GET/DELETE /products/:externalId, or POST /try-on referencing a missing externalId, or stats endpoint with a bad id.
GENERATION_NOT_FOUND404GET /generations/:id for an id that doesn’t exist on the account.
ACCOUNT_NOT_FOUND404Account lookup failed (very rare — the API key check usually fails first).
ROUTE_NOT_FOUND404Request hit a path that doesn’t exist on /tryon/v1/*. For unknown routes the standard error shape may not apply.

Conflicts

CodeStatusWhen you’ll see it
RESERVED_EXTERNAL_ID409POST /products with an externalId starting with _anon_. That prefix is reserved for server-generated IDs.

Server

CodeStatusWhen you’ll see it
INTERNAL_ERROR500Unexpected failure on our side. Safe to retry with exponential backoff. Contact support if it recurs.

Handling errors in code

Switch on code and let the message bubble up to your logs/UI for debugging. Don’t parse message — it’s polished text, not a stable identifier.
import requests

r = requests.post(f"{BASE}/tryon/v1/try-on", headers=HDR, json=payload)
if not r.ok:
    err = r.json()
    code = err.get("code")
    if code == "INSUFFICIENT_CREDITS":
        notify_billing(err["message"])
    elif code == "PRODUCT_NOT_FOUND":
        log_missing_product(payload["product"]["externalId"])
    elif code == "VALIDATION_FAILED":
        # err["details"] = [{ "path": "...", "message": "..." }, ...]
        log_validation_issues(err["details"])
    else:
        log_unexpected(err)
    r.raise_for_status()

Retry guidance

CodeRetry?Notes
MISSING_API_KEYNoFix the request.
INVALID_API_KEYNoRe-issue the key from the dashboard if it was rotated.
VALIDATION_FAILEDNoFix the offending fields listed in details.
PRODUCT_NOT_FOUNDNoInline-upsert the product (send a full payload on the next call), or store it via POST /products.
GENERATION_NOT_FOUNDNoThe id is wrong or belongs to a different account.
CUSTOMER_IMAGE_FETCH_FAILEDSometimesRetry once if it might be transient; otherwise fix the source URL.
PRODUCT_IMAGE_FETCH_FAILEDSometimesSurfaces on GET /generations/:id. Retry once for transient hiccups; otherwise fix the product image URL.
FILE_TOO_LARGENoResize/compress before resending.
INSUFFICIENT_CREDITSAfter top-upTop up from the Genlook dashboard.
QUOTA_EXCEEDEDYes (back off)Usually transient. Exponential backoff.
RATE_LIMITEDYes (back off)Honour Retry-After if present; otherwise exponential backoff.
INTERNAL_ERRORYes (back off, ≤3×)Exponential backoff. Contact support if it persists.