Error catalog (v2)
Every v2 failure has the same body:
{ "error": { "code": "invalid_request", "message": "user_id must be 1-128 characters of A-Za-z0-9._:@-" }, "meta": { "request_id": "b6f0…" }}error.code— a stable, lowercase slug. Branch on this.error.message— human-readable detail. It is usually a string; on validation failures from the underlying services it can be an object or array of field errors, so decode it defensively.meta.request_id— present on every response. Log it; it is what support needs to trace a call.
Codes
| HTTP | error.code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed request part, missing user_id / phrase / ids, user_id failing the pattern, unknown service name, provider outside inhouse/idscan, more than 100 IDs in a batch lookup |
| 400 | bad_request | Rejected by the shared request pipeline (unreadable multipart, missing file part) |
| 401 | invalid_api_key | Missing Authorization header, malformed Bearer value, or an API key that is unknown, revoked or has no services enabled |
| 401 | invalid_token | Endpoint needs a Console user session and the token is not one |
| 401 | unauthorized | Credential rejected by the shared auth pipeline |
| 403 | service_not_enabled | None of the services the endpoint needs is enabled on the API token |
| 403 | forbidden | Consent missing, insufficient project role, or the resource belongs to another project |
| 404 | not_found | Unknown session, deepfake check, scoring preset or reference image |
| 409 | conflict | Conflicting state — e.g. a consent version already approved |
| 422 | unprocessable_entity | Input understood but unusable: no face detected, unreadable audio, unrecognisable document |
| 500 | internal_error | Gateway-side failure — safe to retry with backoff |
| 500 | internal_server_error | Failure surfaced by an upstream service |
Failures that are not HTTP errors
Two cases return 200 and must be inspected in the payload:
- A service failed. Verification endpoints run services in parallel; a
failing one appears as
{"error": "…"}under its own key indata, while the call succeeds. See Verification. - An enrollment was rejected.
POST /v2/enrollments/*returns200withmeta.messageset tofailed to enroll face/failed to enroll voiceand a non-zeroenrollment_result.codeor astatusoferror/qafailed. See Enrollments.
A missing consent is a third special case: Face Recognition and Voice
Recognition are silently dropped from the call rather than failing it, and the
response carries the header X-Removed-Services: true.
Retry guidance
| Situation | Do |
|---|---|
500 / internal_error | Retry with exponential backoff; these are transient. |
401 | Do not retry. Re-check the token and which credential the endpoint needs. |
403 service_not_enabled | Do not retry. Enable the service on the API token in the Console. |
403 forbidden (consent) | Collect consent, then retry. |
400 / 422 | Do not retry the same payload — fix the request or re-capture the media. |
Never retry a non-idempotent call blindly: enrollment and deepfake submission
both create records. Use a fresh session_id per attempt so the retry is
distinguishable in the Console.