Errors
Every failure from this API — every status, every endpoint — answers with the same envelope:
The same request_id is also sent as the X-Request-Id response header.
The contract is split down the middle
That split is the whole design. A stable machine code means you never have to write
if "insufficient" in message.lower(), which is the pattern that breaks when
someone improves a sentence. So:
A 400 INVALID_REQUEST carries one details[] entry per rejected field, and its
details[].code — duplicate_product_category, for instance — is as stable as
error.code. Branch on either; never on a message.
Adding a new code is a normal, non-breaking change, so treat an unrecognised code as “a failure of this status class” rather than crashing on it.
Statuses are never collapsed
An authentication failure, an exhausted quota, a rate limit and a dependency outage
each get their own status. None of them are answered as 400.
The point of not collapsing them is that your retry logic is a function of the
status. If every failure were a 400, a correct client would be impossible to
write: it could not distinguish “you are wrong” from “come back in a second”.
Two pairs worth knowing apart:
429 TENANT_QUEUE_LIMIT_EXCEEDEDis your account’s own queue being full.503 QUEUE_CAPACITY_EXCEEDEDis ours. Both are retryable; only the first is something you can fix by submitting less.503 SERVICE_UNAVAILABLEduring authentication is a503and not a401because your key was never checked. Do not discard a key over it.
Work failures are not HTTP failures
A submission that returns 202 and then fails during analysis reports that failure
on a 200 poll, with the same envelope inside the job:
See Asynchronous jobs. An HTTP error code is about your request;
job.status is about the work.
What an error will never contain
By design, and enforced in our own tests: no image bytes, no facial landmarks, no API key or fragment of one, no internal file path, no storage or cache key, no stack trace, no upstream URL.
If you are wiring errors into your own logs and alerts, the safe fields are
error.code, error.message, request_id, the job id, the operation, the HTTP
status, and your own timing. That is also exactly the set support will ask for.
The full list
Error codes is generated from the code that raises them, so it cannot drift from what you will actually receive.