> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chmodlab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Decisions and issues

> How a verdict is reached, what an issue is, and how to act on each kind.

When analysis finishes, a transaction carries one **decision** and a list of **issues**
explaining it. The decision is what you act on; the issues are why.

## The decision

<CardGroup cols={3}>
  <Card title="Approved" icon="circle-check">
    Every applicable check ran and none rejected. May still carry warnings.
  </Card>

  <Card title="Rejected" icon="circle-xmark">
    At least one check rejected. Every reason is listed.
  </Card>

  <Card title="Undetermined" icon="circle-question">
    The analysis could not run. A system condition, not a verdict on the person.
  </Card>
</CardGroup>

The rule is mechanical and worth memorising:

| If | Then the decision is |
| - | - |
| The analysis could not run | Undetermined |
| Any finding is a rejection | Rejected |
| Otherwise | Approved |

Warnings and informational findings never move the decision. There is no score, no threshold, no grey zone: a single rejecting finding rejects.

<Warning>
  Undetermined is not "maybe". It means the analysis did not complete — a configuration that could not be applied, a malformed session, or a provider failure. Treat it as an operational event: fix the cause if it is yours, retry with a new transaction, and never count it against the user.
</Warning>

## Issues

Each finding is an **issue** with four parts:

| Part | What it is | How to use it |
| - | - | - |
| Type | Reject, warn or info | Tells you whether it blocked |
| Code | A stable identifier such as `EXPIRED_DOCUMENT` | **Branch on this.** Codes are never renamed once published |
| Message | English prose for your operators | Log it. Do not show it to end users |
| Details | Extra context, when it adds something not available elsewhere | Scores, historical values, the offending field |

### Three kinds of issue

**Fixed-type issues** always have the same type. An expired document is always a rejection; a skipped face comparison on a first transaction is always informational.

**Policy-driven issues** take their type from your configuration. An emulator detection is a rejection if you configured it that way, a warning if you chose that, and is not reported at all if you chose to ignore it. This is how you tune strictness without changing code.

**Advisory issues** tell you about the analysis itself rather than the person: a rule that could not be evaluated because a signal was missing, a configuration that is valid but probably not what you meant, a comparison that was skipped. They are warnings or informational and never block.

The full catalogue of codes is in the API Reference under [Issue codes](/results/issue-codes).

### All checks run to completion

chmod does not stop at the first rejection. If a document is expired *and* the surname
does not match *and* the device is an emulator, you get all three findings at once. You
never have to resubmit to discover the next problem, and your operators see the whole
picture on the first look.

## Block statuses

Alongside the overall decision, each analysed area reports its own status so you can see
*where* a rejection came from without scanning every code:

| Area | Status | Rejected when |
| - | - | - |
| Document | Passed / Rejected | Any document check rejected |
| Liveness | Passed / Rejected | Any liveness check rejected |
| Face comparison | Passed / Rejected / none | Any comparison failed; none if no comparison ran |

Device, customer and configuration findings belong to no area. They sit in the issue list and affect the decision directly.

## Acting on the result

Group findings by what can be done about them, not by which check produced them:

| Bucket | Examples | Right response |
| - | - | - |
| **Retryable capture** | Blurred document, sunglasses, closed eyes | Let the person try again, with guidance |
| **Fraud signal** | Manipulated document, photocopy, document face does not match the selfie | No retry. Manual review |
| **Policy decision** | Document not accepted, expired, name does not match | Explain what is needed; a retry with the right document may work |
| **Your configuration** | A rule could not be applied | An integration bug. Fix it and create a new transaction |
| **Operational** | Provider failure, undetermined decisions | Retry with a new transaction; alert if it persists |

<Warning>
  Cap retries and route fraud signals to a human. An unlimited retry on a manipulated document turns your flow into a testing ground where an attacker learns which forgery passes by trying until one does.
</Warning>

## What to show the person

The message on each finding is written for your engineers and operators. It is precise about *which* check failed — which is exactly what a fraudster wants to know, and more than a genuine user needs.

Map codes to your own copy instead:

| Finding | What the person sees |
| - | - |
| Document image unusable | "We couldn't read your document clearly. Try again in better light." |
| Sunglasses detected | "Please remove your sunglasses and try again." |
| Expired document | "This document has expired. Please use a valid one." |
| Document not accepted | "We can't accept this type of document. Please use one of: …" |
| Any fraud signal | "We couldn't verify your identity. Our team will be in touch." |

A genuine user with a bad photo gets actionable guidance. A fraudster gets nothing useful.

## What to store

Keep the decision, the issue codes, the transaction id and the timestamp. That is enough for your audit trail, your analytics and your support conversations, and it avoids holding the document images and personal data that live inside the full result. Fetch the full result by id when you genuinely need it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.