Skip to main content
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.

Unprocessable product

CodeStatusWhen you’ll see it
MISSING_REQUIRED_CLASSIFICATION422The engine selected for this product type requires a product image with a specific classification (e.g. pasties need an image classified on_model), and none of the product’s images qualifies. details.key names the missing classification. Add a suitable image or pass an override classification. Also surfaces on GET /generations/:id as errorCode when the check runs during processing.

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.
GENERATION_FAILED500The generation pipeline failed mid-way (model unavailable, upstream timeout, or another internal hiccup). Surfaces on GET /generations/:id once the generation transitions to FAILED. Safe to retry.

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.
MISSING_REQUIRED_CLASSIFICATIONNoAdd a product image that satisfies the classification named in details.key, or pass an override classification.
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.
GENERATION_FAILEDYes (back off, ≤3×)Surfaces on GET /generations/:id. Usually transient — retry the request. Contact support if it persists.