Skip to navigation

Troubleshooting

Symptom, likely cause, and what to collect before asking for help.

What to collect, always

request_id from the error body or the X-Request-Id header
job id if a job was created (job_...)
asset id if the problem is an upload or a download (asset_...)
operation e.g. POST /v1/skin/analyses
HTTP status and error.code
timestamps when you sent it, when you gave up

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”

You seeCauseDo
401 INVALID_API_KEYheader absent or misspelled, a wrong value, or a key that is revoked, expired or disabledit is X-API-Key, on every call; check which base URL and which key you are using
403 INSUFFICIENT_SCOPEthe key is usable and lacks this operation’s scopereconfigure the key, do not retry
413 PAYLOAD_TOO_LARGEthe upload is over the size limitdownscale before sending; you do not need full-resolution capture
415 UNSUPPORTED_MEDIA_TYPEnot JPEG/PNG/WebP, or bytes that do not match the declared Content-Typesend the raw bytes with the matching Content-Type
422 UNPROCESSABLE_IMAGEa recognised image over the dimension or pixel limit, or undecodablere-encode or downscale
400 IDEMPOTENCY_KEY_REQUIREDa submission without Idempotency-Keysend one, derived from your own operation
400 INVALID_REQUESTa malformed JSON bodyread error.details[]: each entry names the field and a code
404 ASSET_NOT_FOUNDthe asset id is not in this environment, or never existedcheck you are submitting the id POST /v1/assets returned
410 ASSET_EXPIREDthe input asset passed its expires_atupload again
402 INSUFFICIENT_CREDITSout of creditstop up; retrying will not help

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.