> ## 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.

# Reading a result

> Every field inside result_data — decision, issues, extracted document data and biometrics.

[Get transaction](/api-reference/transactions/get-transaction) returns the transaction envelope — identifiers, timestamps and status — and, once analysis has finished, a `result_data` object carrying the decision, every reason behind it, and everything chmod extracted along the way.

```json lines theme={null}
{
  "id": "34dc4204-2b57-42ae-a3bc-1d114935b98f",
  "customer_id": "4a63b11c-803d-4126-bf91-d5f8290ff0a5",
  "created_at": "2026-09-10T14:22:00.000Z",
  "initiated_at": "2026-09-10T14:23:12.450Z",
  "completed_at": "2026-09-10T14:25:30.110Z",
  "processing_at": "2026-09-10T14:25:30.980Z",
  "finished_at": "2026-09-10T14:25:44.220Z",
  "status": "FINISHED",
  "result_data": {
    "decision": "APPROVED",
    "issues": [],
    "metadata":        { "device": {}, "network": {}, "geolocation": {} },
    "document":        { "status": "PASSED", "data": {} },
    "liveness":        { "status": "PASSED", "data": {} },
    "face_comparison": { "status": "PASSED", "data": {} }
  }
}
```

## Envelope

<ResponseField name="id" type="string (UUID)">
  The transaction id.
</ResponseField>

<ResponseField name="customer_id" type="string (UUID)">
  The customer this transaction belongs to.
</ResponseField>

<ResponseField name="created_at" type="string (ISO-8601)">
  When your backend opened the transaction. Format `YYYY-MM-DDTHH:mm:ss.SSSZ`, UTC.
</ResponseField>

<ResponseField name="initiated_at" type="string (ISO-8601) | null">
  When the SDK started the session on the device.
</ResponseField>

<ResponseField name="completed_at" type="string (ISO-8601) | null">
  When the user finished capture.
</ResponseField>

<ResponseField name="processing_at" type="string (ISO-8601) | null">
  When analysis started.
</ResponseField>

<ResponseField name="finished_at" type="string (ISO-8601) | null">
  When analysis ended.
</ResponseField>

<ResponseField name="status" type="string">
  `CREATED`, `INITIATED`, `COMPLETED`, `PROCESSING`, `FINISHED`, `EXPIRED` or `FAILED`. `result_data` is present only for `FINISHED` and `FAILED`.
</ResponseField>

## Decision

<ResponseField name="result_data.decision" type="string">
  `APPROVED`, `REJECTED` or `UNDETERMINED`.
</ResponseField>

The rule is mechanical: if any issue has `type: "REJECT"`, the decision is `REJECTED`.
If the analysis could not run at all, it is `UNDETERMINED`. Otherwise it is `APPROVED`.

`WARN` and `INFO` issues never change the decision. An `APPROVED` transaction can — and
often does — carry warnings worth reviewing.

<Warning>
  `UNDETERMINED` is not a middle ground between approved and rejected. It means the
  analysis did not complete: a malformed request, an invalid configuration, or a provider
  failure. Treat it as a system error to retry or escalate, never as a soft rejection of
  the user.
</Warning>

## Issues

<ResponseField name="result_data.issues" type="array">
  Every finding from the analysis. Always present; empty on a clean approval.

  <Expandable title="issue">
    <ResponseField name="type" type="string">
      `REJECT`, `WARN` or `INFO`.
    </ResponseField>

    <ResponseField name="code" type="string">
      Stable identifier, for example `EXPIRED_DOCUMENT`. **This is the contract** — branch
      your logic on it. Codes are never renamed once published.
    </ResponseField>

    <ResponseField name="message" type="string">
      Human-readable description, always in English. Written for your operators, not your
      end users — do not show it verbatim to the person being verified.
    </ResponseField>

    <ResponseField name="details" type="object | null">
      Extra context, present only when it adds something you cannot get elsewhere in the
      result. `null` for most codes.
    </ResponseField>
  </Expandable>
</ResponseField>

All applicable checks run to completion, so `issues[]` lists every problem at once rather
than surfacing them one resubmission at a time.

```json lines theme={null}
{
  "issues": [
    {
      "type": "REJECT",
      "code": "SURNAME_MISMATCH",
      "message": "The surnames on the document do not match the expected value.",
      "details": { "score": 0.41, "threshold": 0.85 }
    },
    {
      "type": "WARN",
      "code": "DEVELOPER_MODE_ENABLED",
      "message": "The device has developer mode enabled.",
      "details": null
    }
  ]
}
```

See [Issue codes](/results/issue-codes) for the full catalogue.

## Block statuses

Alongside the overall decision, each area reports its own status:

<ResponseField name="result_data.document.status" type="string">
  `PASSED` or `REJECTED`. `REJECTED` when any document-related check rejected.
</ResponseField>

<ResponseField name="result_data.liveness.status" type="string">
  `PASSED` or `REJECTED`.
</ResponseField>

<ResponseField name="result_data.face_comparison.status" type="string | null">
  `PASSED` or `REJECTED`, or `null` when no comparison ran.
</ResponseField>

Which blocks are present depends on the transaction type:

| Block | `DOCUMENT_AND_BIOMETRIC` | `DOCUMENT_ONLY` | `BIOMETRIC_ONLY` |
| - | - | - | - |
| `document` | object | object | `null` |
| `liveness` | object | `null` | object |
| `face_comparison` | object | object or `null` | object or `null` |
| `metadata` | always | always | always |
| `issues` | always | always | always |

Device, customer and configuration issues belong to no block — they live in `issues[]` and
affect the decision directly.

## Document data

`document.data` holds everything read off the document. The printed fields:

```json lines theme={null}
{
  "document_type": "NATIONAL_ID",
  "document_number": "20123456",
  "document_number_raw": "20.123.456",
  "issuing_country": "AR",
  "issuing_authority": "RENAPER",
  "issue_date": "2019-04-12",
  "expiry_date": "2034-04-12",
  "given_name": "ANA MARIA",
  "surname": "PEREZ",
  "full_name": "PEREZ, ANA MARIA",
  "gender": "F",
  "date_of_birth": "1996-01-09",
  "nationality": "AR",
  "front_document_image_s3": "s3://...",
  "back_document_image_s3": "s3://..."
}
```

<Info>
  `document_number` is normalised — alphanumeric only. `document_number_raw` is what was
  actually printed, dots and hyphens included. Store the raw value if you ever display it
  back to the user; compare on the normalised one.
</Info>

When the document carries them, three more objects appear:

<ResponseField name="address" type="object | null">
  Structured address — street, city, administrative areas, postal code, country — plus the
  raw string as printed. `null` when the document has no address.
</ResponseField>

<ResponseField name="mrz" type="object | null">
  The machine-readable zone: type (`TD1`, `TD2`, `TD3`), the raw lines, and every parsed
  field with its check digit and a `valid_*` boolean. `null` when the document has no MRZ.
</ResponseField>

<ResponseField name="barcode" type="object | null">
  The decoded barcode: format (`PDF417`, `QR`, ...), standard (`AAMVA`, `ICAO`, ...), the
  raw payload, and parsed fields including address and document-specific extras such as
  licence class and restrictions. `null` when the document has no barcode.
</ResponseField>

chmod cross-checks the printed data, the MRZ and the barcode against each other. When they
disagree it emits `DOCUMENT_DATA_MISMATCH` with the offending field — a strong tampering
signal, since altering a printed field without recomputing the MRZ check digits is the
most common forgery.

## Liveness data

```json lines theme={null}
{
  "liveness": {
    "status": "PASSED",
    "data": {
      "liveness_face_image_s3": "s3://...",
      "confidence_score": 0.97,
      "face_attributes": {
        "age_range":     { "low": 28, "high": 38 },
        "gender":        { "value": "F",   "confidence": 0.99 },
        "eyeglasses":    { "value": false, "confidence": 0.98 },
        "sunglasses":    { "value": false, "confidence": 0.99 },
        "eyes_open":     { "value": true,  "confidence": 0.97 },
        "beard":         { "value": false, "confidence": 0.95 },
        "mustache":      { "value": false, "confidence": 0.96 },
        "face_occluded": { "value": false, "confidence": 0.98 },
        "pose":          { "roll": 1.2, "yaw": -3.4, "pitch": 0.8 }
      }
    }
  }
}
```

Age comes back as a **range**, not a number. Every attribute carries its own confidence,
which is what lets you tell a confident rejection from a marginal one.

## Face comparison data

```json lines theme={null}
{
  "face_comparison": {
    "status": "PASSED",
    "data": {
      "comparison": [
        { "type": "DOCUMENT_VS_LIVENESS",     "confidence_score": 0.97, "matched": true },
        { "type": "LIVENESS_VS_IDENTITY_REF", "confidence_score": 0.94, "matched": true }
      ]
    }
  }
}
```

## Metadata

`metadata` carries the device, network and geolocation signals reported during capture —
always, whatever the decision. See
[Device policy](/configuration/device-policy#where-the-signals-appear) for the full
shape.

## Handling the result

```typescript lines theme={null}
function handleResult(tx: Transaction) {
  const { decision, issues } = tx.result_data;

  switch (decision) {
    case "APPROVED":
      // Warnings do not block, but they are worth recording.
      const warnings = issues.filter((i) => i.type === "WARN");
      if (warnings.length) flagForReview(tx.id, warnings);
      return approve(tx.customer_id);

    case "REJECTED":
      // Branch on `code`, never on `message`.
      const blocking = issues.filter((i) => i.type === "REJECT");
      return reject(tx.customer_id, blocking.map((i) => i.code));

    case "UNDETERMINED":
      // A system failure, not a verdict on the user. Let them try again.
      return escalate(tx.id, issues);
  }
}
```

<Warning>
  Branch on `code`, never on `message`. Messages are prose and may be reworded; codes are
  a stable contract.
</Warning>


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