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.
{
"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:
{
"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
| Code | Status | When you’ll see it |
|---|
MISSING_API_KEY | 401 | x-api-key header is missing. |
INVALID_API_KEY | 401 | The key was rejected — wrong, revoked, or inactive account. |
Authorization
| Code | Status | When you’ll see it |
|---|
FORBIDDEN | 403 | Requested resource (product, generation) belongs to a different account. |
Request validation (400)
| Code | When you’ll see it |
|---|
VALIDATION_FAILED | Zod rejected the request body. details[] lists { path, message } for every offending field. |
EXTERNAL_ID_REQUIRED | POST /products without externalId. |
PRODUCT_IMAGES_REQUIRED | Creating or one-shotting a product without any image. |
CUSTOMER_IMAGE_REQUIRED | POST /try-on without a customer object. |
FILE_REQUIRED | POST /images/upload called with no file field. |
MULTIPART_FILE_NOT_FOUND | A fileKey referenced from data doesn’t appear in the multipart payload. |
INVALID_IMAGE_INPUT | An images[] entry has neither url nor fileKey. |
INVALID_IMAGE | Uploaded bytes are too small to be an image, or otherwise malformed before format detection. |
UNSUPPORTED_IMAGE_TYPE | The uploaded file isn’t JPEG, PNG, WebP, or HEIC. |
CUSTOMER_IMAGE_FETCH_FAILED | customer: { url } could not be downloaded — DNS failed, host unreachable, or the URL returned a non-2xx status. |
PRODUCT_IMAGE_FETCH_FAILED | A 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
| Code | Status | When you’ll see it |
|---|
FILE_TOO_LARGE | 413 | Any single image attachment exceeds 10 MB (customer image or product image). |
Billing
| Code | Status | When you’ll see it |
|---|
INSUFFICIENT_CREDITS | 402 | Account has 0 credits remaining. Top up and retry. |
QUOTA_EXCEEDED | 402 | The account-level quota check rejected the call. Back off and retry — these are usually transient. |
Rate limiting
| Code | Status | When you’ll see it |
|---|
RATE_LIMITED | 429 | Per-account request budget exceeded. Back off and retry. Burst limits are higher than steady-state — see your dashboard. |
Not found
| Code | Status | When you’ll see it |
|---|
PRODUCT_NOT_FOUND | 404 | GET/DELETE /products/:externalId, or POST /try-on referencing a missing externalId, or stats endpoint with a bad id. |
GENERATION_NOT_FOUND | 404 | GET /generations/:id for an id that doesn’t exist on the account. |
ACCOUNT_NOT_FOUND | 404 | Account lookup failed (very rare — the API key check usually fails first). |
ROUTE_NOT_FOUND | 404 | Request hit a path that doesn’t exist on /tryon/v1/*. For unknown routes the standard error shape may not apply. |
Conflicts
| Code | Status | When you’ll see it |
|---|
RESERVED_EXTERNAL_ID | 409 | POST /products with an externalId starting with _anon_. That prefix is reserved for server-generated IDs. |
Server
| Code | Status | When you’ll see it |
|---|
INTERNAL_ERROR | 500 | Unexpected 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
| Code | Retry? | Notes |
|---|
MISSING_API_KEY | No | Fix the request. |
INVALID_API_KEY | No | Re-issue the key from the dashboard if it was rotated. |
VALIDATION_FAILED | No | Fix the offending fields listed in details. |
PRODUCT_NOT_FOUND | No | Inline-upsert the product (send a full payload on the next call), or store it via POST /products. |
GENERATION_NOT_FOUND | No | The id is wrong or belongs to a different account. |
CUSTOMER_IMAGE_FETCH_FAILED | Sometimes | Retry once if it might be transient; otherwise fix the source URL. |
PRODUCT_IMAGE_FETCH_FAILED | Sometimes | Surfaces on GET /generations/:id. Retry once for transient hiccups; otherwise fix the product image URL. |
FILE_TOO_LARGE | No | Resize/compress before resending. |
INSUFFICIENT_CREDITS | After top-up | Top up from the Genlook dashboard. |
QUOTA_EXCEEDED | Yes (back off) | Usually transient. Exponential backoff. |
RATE_LIMITED | Yes (back off) | Honour Retry-After if present; otherwise exponential backoff. |
INTERNAL_ERROR | Yes (back off, ≤3×) | Exponential backoff. Contact support if it persists. |