Skip to main content
Every finding in issues[] carries a code. Codes are a stable contract: once published, a code is never renamed or repurposed. Branch your logic on code, never on message.

Types

Some codes are marked policy-driven below: their type is not fixed, it comes from the REJECT / WARN / IGNORE action you configured for that policy. With IGNORE, nothing is emitted at all.
Most codes carry details: null. details is populated only when it tells you something you cannot get from elsewhere in the result — a similarity score you cannot recompute, or a historical value that does not appear anywhere else. Thresholds and expected values are omitted because they are in the config you sent; extracted values are omitted because they are in result_data.

Request and session

These abort the analysis. decision is UNDETERMINED. These generally mean something went wrong between the SDK and the API rather than anything about the user. Retry with a new transaction.

Configuration — blocking

Your config could not be applied. Analysis does not run; decision is UNDETERMINED. These are integration bugs, not user failures. Fix the config and create a new transaction.

Configuration — advisory

The config is valid but probably not what you meant. Analysis proceeds normally.

Engine

decision is UNDETERMINED. No details — the diagnostics are internal. Retriable. Create a new transaction rather than treating the user as rejected.

Customer

Device

All policy-driven via device_policy. No details — the signals behind them are in result_data.metadata.

Location unavailable

Signal missing with policy active

Emitted only when the corresponding policy is not IGNORE. Never change the decision — they tell you a rule you configured could not run.

Document — capture and legibility

Most of these are recoverable by the user — better lighting, a flatter surface, no glare. Worth surfacing as “try again” rather than a hard failure.

Document — authenticity

No details beyond one case: naming the check that caught the forgery would tell a fraudster what to fix.

Document — eligibility

DOCUMENT_TYPE_NOT_SUPPORTED and DOCUMENT_NOT_ELIGIBLE are worth distinguishing in your UI: the first means “we cannot read this”, the second means “you asked us not to accept it”.

Document — customer history

The customer’s history is not in the result, so all three carry it in details.

Data matching

All REJECT. Only the fuzzy comparisons carry details — you cannot recompute a similarity score, but you already know the exact values you sent.

Liveness

All REJECT. Scores and facial attributes are already in result_data.liveness.data. Sunglasses, closed eyes, occlusion and poor quality are all things the user can fix. Offering a retry converts most of them.

Face comparison

FACE_COMPARISON_SKIPPED_NO_REFERENCE is normal and expected on a customer’s first transaction. It is INFO, not a rejection.

Not yet enforced

These codes are defined and their policies are accepted by the API, but the analysis engine does not evaluate them yet, so they are not currently emitted. Configure them if you want the intent recorded; do not rely on them as active protection. Blocklist matches deliberately carry no details: exposing why someone was listed is exactly the information that makes a blocklist easy to evade.

Handling codes in your app

Group codes by what the user can do about them, not by which check produced them:
Offering an unlimited retry on a fraud signal turns your flow into a testing ground: an attacker learns which forgery passes by trying until one does. Cap retries and route the codes above to a human.