Troubleshooting
What to collect, always
Never attach the image, the API key, or any fragment of the key. If a specific photograph is genuinely necessary to reproduce a problem, say so and we will arrange a path for it — do not paste it into a ticket.
”My upload or submission is rejected”
A 401 never says why the key is unusable, on purpose. If a key that worked
yesterday answers 401 today, check whether it was rotated or revoked on your side
before anything else.
”The job never leaves queued or running”
First, check you are honouring Retry-After — a tight loop can be rate limited
into looking like a stall.
Then: a job stays queued when nothing is consuming the queue, and running when
the work is genuinely slow (large image, many requested concerns and
visualizations). Reduce concerns and visualizations to what you display and see
whether the duration changes.
Do not resubmit blindly. With the same Idempotency-Key, a resubmission returns
the same job and tells you nothing new; without one, it starts a second billable
analysis of the same image.
If a job stays queued well past its Retry-After hints, contact support with the
job id and the request_id of a poll; see Support.
”The job failed with INPUT_UNPROCESSABLE”
This is a job-level failure — HTTP 200, "status": "failed", error.code of
INPUT_UNPROCESSABLE: no usable face was found, or the image quality was too low.
Retry once, under a new Idempotency-Key, with a single direct, upright capture:
- no reflective glass or a reflected second face crossing the subject;
- the whole face outline visible, with margin at the sides and chin;
- not an edge-to-edge close crop.
Those conditions are supported by fixture evidence. No numeric threshold exists for face size, lighting, pose or crop, and a clear frontal capture can still hit a known detector gap.
If a compliant retry also fails, stop. Record the job id and request_id and ask.
Changing the image repeatedly until one passes produces an integration that works on
your test photo and fails on your customers’.
”The result looks wrong”
First check the direction: a higher skin score means the concern is less
visible. A 9.1 for acne is a good reading. An inverted axis in a chart accounts for a good share of “the results look
backwards” reports.
For makeup, check the parity status before treating a difference from another renderer as a defect: pixel parity is proven for no category today. Makeup try-on
Then record the job id, request_id, operation, the products or concerns you
requested, the analysis_version, and a safe reproduction reference.
”The result download 404s”
On GET /v1/assets/{asset_id}/content, 404 ASSET_NOT_FOUND means the asset expired,
is not in this environment, or never existed. A result asset expires with its job, at the
expires_at it carries, and the href is not storage. Fetch what you need promptly
and keep your own copy.
404 ASSET_CONTENT_UNAVAILABLE means you asked for the content of an input asset.
Only result assets serve content; you already have the input’s bytes.
A job poll past the job’s expires_at answers 404 JOB_NOT_FOUND the same way.
A 503 on either route is not expiry: it means ask again, after its Retry-After.
”I get 409 on a retry”
409 IDEMPOTENCY_KEY_REUSED means the key was reused with a different request body.
That is your code, not ours: the key is a claim that two requests are identical.
Version the key when the content changes. Do not retry it.
409 IDEMPOTENCY_IN_PROGRESS is different — it is retryable, it carries a
short Retry-After and no job id, and it clears.
”Submitting a URL fails”
Expected. There is no remote-URL submission, and a submission names only an asset id.
Upload the bytes to POST /v1/assets from your own server.
”I get 429 under load”
429 TENANT_QUEUE_LIMIT_EXCEEDED is your own environment’s queue being full — submit
less, or spread it out. 429 RATE_LIMITED is request rate. 503 QUEUE_CAPACITY_EXCEEDED is ours, not yours.
All three are retryable with Retry-After. If you are hitting the first one
steadily, the fix is concurrency control on your side rather than more retries.