API

Errors

The codes this API actually returns, and the ones it never did.

This page was rewritten on 2026-09-13 because almost none of it was true. It documented 21 error codes, of which 15 did not exist anywhere in the codebase — no route returned them, no library raised them, nothing ever had. It also gave the wrong response shape: it told you to branch on a code field, and there is no code field.

That is the same defect that was found on the API reference on 2026-09-12 and corrected there. It was written from a specification rather than from the server, and nothing in the build catches prose. If you wrote error handling against the old page, the branches you wrote for INSUFFICIENT_CREDITS, NO_PERSON_DETECTED, CONTENT_POLICY, QUOTA_EXCEEDED, GENERATION_TIMEOUT and the rest have never fired and never will.

The shape

json
{ "error": "NO_CREDITS", "message": "This workspace is out of credits." }

The field is error. Branch on it, never on message — messages are written for humans and get reworded. The HTTP status carries the same information; there is no status field in the body.

What you can actually receive

Any code not in this list is collapsed to INTERNAL_ERROR with a generic message before it reaches you, and the real one is recorded on our side. That is deliberate: an unrecognised code is assumed to be internal, because the cost of guessing wrong in that direction is a confusing message, and the cost of guessing wrong in the other is showing you a constraint name or a stack. The allowlist lives in INTEGRATOR_SAFE and is the authority for this table.

Auth

CodeHTTPMeaning
UNAUTHORIZED401No x-api-key header, or the key is unknown
KEY_REVOKED401The key existed and has been revoked
PUBLISHABLE_KEY_REFUSED401A publishable key was sent to a server endpoint that requires a secret one
NOT_FOUND404No such generation, customer or resource on this account

Request shape

CodeHTTPMeaning
BAD_REQUEST400Body is not JSON, or a required field is malformed
PERSON_REQUIRED400No person supplied
GARMENT_REQUIRED400No products supplied
TOO_MANY_GARMENTS400More than 6
BAD_IMAGE400The bytes did not decode as an image
EMPTY_IMAGE400Zero-length image
UNSUPPORTED_IMAGE_TYPE400Not JPEG, PNG, WebP or HEIC
FILE_TOO_LARGE413Over the size limit

Catalogue

CodeHTTPMeaning
PRODUCT_NOT_FOUND400An externalId this account has never synced. There is no inline upsert
PRODUCT_IMAGES_REQUIRED400The product exists and has no usable image

Account state

CodeHTTPMeaning
NO_CREDITS402Buy a credit pack and retry
RATE_LIMITED429Retryable. A retry-after header says how long
NO_MODEL503This deployment has no try-on model configured
MODEL_NOT_FOUND400The modelId you sent is not in the routable pool
BAD_MODEL400The modelId you sent is not a model code (mdl_…) of this platform
UNAVAILABLE503Try-on is briefly unavailable

Screening

Screening runs before any generation model sees an image, and a refusal costs no credit.

CodeHTTPMeaning
IMAGE_REJECTED422The person photograph was refused. The message is deliberately generic
GARMENT_REJECTED422The product image was refused. Replace it in your catalogue
SCREENING_UNAVAILABLE503Screening could not reach its provider, so the try-on was refused rather than allowed through. Retryable

SCREENING_UNAVAILABLE is the one worth handling deliberately. It fails closed on purpose: an empty provider balance surfaces here rather than as a billing error, and it is what a four-day outage looked like in September 2026 — every try-on politely refused, nothing thrown.

Image quality

A separate question from screening: not may we use this photograph, but can we. These run alongside screening, also before any generation model, and a refusal also costs no credit.

CodeHTTPMeaning
IMAGE_TOO_SMALL422The person photograph is below the minimum on its shortest side. The message names the actual size
IMAGE_TOO_BLURRY422The person photograph is severely out of focus
IMAGE_BAD_ASPECT422The person photograph is an extreme shape — a banner rather than a photograph of a person
GARMENT_TOO_SMALL422Same, for a product image. The message names which garment
GARMENT_TOO_BLURRY422Same, for a product image
GARMENT_BAD_ASPECT422Same, for a product image

Two things worth knowing before you handle these.

The focus check catches severe blur only, and this is a limit of the measure rather than a setting. It is the variance of a Laplacian, which cannot distinguish "out of focus" from "not much in the frame" — in calibration, a photograph at blur radius 2 and a sharp photograph of a plain subject scored 343 and 350. Any threshold that rejects the first rejects the second. So a mildly soft photograph will pass, and you should not treat the absence of these codes as a statement that a photograph is good.

There is no pose or framing check. A photograph cropped at the chest passes all three, and every model we have tested will then invent a torso and arms that were not in your input. If your users upload head-and-shoulders portraits, that is worth catching on your side.

Anything else

CodeHTTPMeaning
INTERNAL_ERROR500Something on our side. The real code is in our logs, not in your response

Storefront widget errors are a shorter list

The widget API is driven by a publishable key that ships in your page source, so its audience is a shopper rather than a developer and it names fewer codes: BAD_IMAGE, UNSUPPORTED_IMAGE_TYPE, EMPTY_IMAGE, FILE_TOO_LARGE, IMAGE_REJECTED, SCREENING_UNAVAILABLE, NO_CREDITS, RATE_LIMITED, SHOPPER_REQUIRED, NO_MODEL, IMAGE_TOO_SMALL, IMAGE_TOO_BLURRY, IMAGE_BAD_ASPECT.

CONSENT_REQUIRED (400) is specific to it: POST /api/storefront/v1/try-on refuses without a consent object, and the check runs before the photograph is decoded.

A few codes come back named but with the message rewritten for a shopper — PRODUCT_NOT_FOUND becomes "This item isn't set up for try-on yet" — so an integrator debugging in the console still sees what happened while the shopper reads something they can act on.

The image-quality codes split along that line rather than being on one side of it. IMAGE_TOO_SMALL, IMAGE_TOO_BLURRY and IMAGE_BAD_ASPECT reach the shopper verbatim, because the shopper is the one holding the photograph and the next thing that happens is they choose a better one. Their GARMENT_* counterparts are rewritten to "This item's photo can't be used for try-on", because the real message names pixel dimensions and is addressed to whoever maintains the catalogue — which is you, reading it in the console, not them.

Billing

Failed generations are never billed. The credit is spent when a generation reaches COMPLETED, so a failure is never debited in the first place — there is nothing to refund.