KYC · API reference

Document

Document Read

POST/v1/idv/readdocuments.read

The full read of an ID document. Returns every container the reader produced, type, printed and MRZ fields, portrait, security and image-quality checks, under data.ContainerList. An optional hint says whether the document matches what you expected.

**Returns**

- data.ContainerList.List[]: the containers; read each by its own result_type, order is not fixed - doc_hint: the match against your expected country and type (when you set a hint)

**Limits**

- Front required; back optional. ~1,400 document types. - Personal values are not masked here; gate who may call it. Use Document Fields for a masked view.

Price: 0.35 credits per successful call.

Body parameters

image_frontstringrequired

Document front. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

image_backstring

Document back. The reverse side, when it carries data. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

scenariostring

Scenario.

FullProcessFullAuthBarcodeMrz
doc_hintobject
Show 2 fields
countrystring

Expected country. ISO code, for example RW or RWA.

typestring

Expected type.

passportnational_iddriving_licenceresidence_permit

Returns

The check ran.

Show the DocumentReadResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The full result of Document Read: everything the check found. See the fields listed in the operation.

warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/idv/read \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image_front": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
    "scenario": "FullProcess"
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_ab957d053778450995a3",
  "latency_ms": 1215,
  "data": {
    "ok": true,
    "data": {
      "ContainerList": {
        "Count": 14,
        "List": [
          {
            "OneCandidate": {
              "AuthenticityNecessaryLights": 3224,
              "CheckAuthenticity": 6817327,
              "DocumentName": "Czech Republic - ePassport (2006)",
              "FDSIDList": {
                "Count": 3,
                "ICAOCode": "CZE",
                "List": [
                  3244,
                  11569,
                  18726
                ],
                "dCountryName": "Czech Republic",
                "dDescription": "Passport",
                "dFormat": 2,
                "dMRZ": true,
                "dType": 11,
                "dYear": "2006",
                "isDeprecated": false
              },
              "ID": 824952191,
              "NecessaryLights": 25165824,
              "OVIExp": 5,
              "P": 0.723872721195221,
              "RFID_Presence": 1,
              "Rotated180": 0,
              "RotationAngle": 0,
              "UVExp": 6
            },
            "buf_length": 8,
            "light": 0,
            "list_idx": 0,
            "page_idx": 0,
            "result_type": 9
          },
          {
            "ImageQualityCheckList": {
              "Count": 6,
              "List": [
                {
                  "featureType": 0,
                  "mean": -4.747840881347656,
                  "probability": 99,
                  "result": 1,
                  "std_dev": 4.747161865234375,
                  "type": 1
                },
                {
                  "featureType": 0,
                  "mean": 0,
                  "probability": 0,
                  "result": 1,
                  "std_dev": 0,
                  "type": 0
                },
                {
                  "featureType": 0,
                  "mean": 0,
                  "probability": 0,
                  "result": 1,
                  "std_dev": 0,
                  "type": 7
                },
                {
                  "featureType": 0,
                  "mean": 0,
                  "probability": 0,
                  "result": 1,
                  "std_dev": 0,
                  "type": 5
                },
                {
                  "featureType": 0,
                  "mean": 0,
                  "probability": 0,
                  "result": 1,
                  "std_dev": 0,
                  "type": 4
                },
                {
                  "featureType": 0,
                  "mean": 0,
                  "probability": 0,
                  "result": 1,
                  "std_dev": 0,
                  "type": 2
                }
              ],
              "result": 1
            },
            "buf_length": 8,
            "light": 0,
            "list_idx": 0,
            "page_idx": 0,
            "result_type": 30
          },
          {
            "Status": {
              "detailsOptical": {
                "docType": 1,
                "expiry": 0,
                "imageQA": 1,
                "mrz": 0,
                "overallStatus": 0,
                "pagesCount": 1,
                "security": 2,
                "text": 0,
                "vds": 2
              },
              "detailsRFID": {
                "AA": 2,
                "BAC": 2,
                "CA": 2,
                "PA": 2,
                "PACE": 2,
                "TA": 2,
                "overallStatus": 2
              },
              "optical": 0,
              "overallStatus": 0,
              "portrait": 2,
              "rfid": 2,
              "stopList": 2
            },
            "buf_length": 84,
            "light": 0,
            "list_idx": 0,
            "page_idx": 0,
            "result_type": 33
          }
        ]
      },
      "TransactionInfo": {
        "DateTime": "2026-10-05T05:36:22.939Z",
        "Tag": "37e5e108-691a-445e-a03d-9a32e49a52d5",
        "TransactionID": "436d80f4-38de-42bd-86f3-056b84a1ad89"
      },
      "ProcessingFinished": 1
    },
    "doc_hint": {
      "country": "CZ",
      "type": "passport",
      "detected": {
        "country": "CZE",
        "category": "Passport"
      },
      "country_matches": true,
      "type_matches": true
    }
  },
  "warnings": [],
  "error": null
}

Document Classify

POST/v1/idv/classifydocuments.classify

Just the identity of the document: its name, category, issuing country, year and a confidence.

**Returns**

- document_name: the full type name - category: Passport, National ID, Driving Licence or Residence Permit - country / country_name: the issuer, ISO alpha-3 and name - state: the state or province that issued it (licences and IDs), or null - size: ID-1 card, ID-2, ID-3 passport page or A4 - has_mrz / has_chip: whether this type carries an MRZ, and a chip - deprecated: true when the issuer no longer issues this type - confidence: 0 to 1

Price: 0.02 credits per successful call.

Body parameters

imagestringrequired

Document. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

Returns

The check ran.

Show the DocumentClassifyResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The answer of Document Classify.

Show 12 fields
document_namestring
doc_typestring
categorystring
countrystring
country_namestring
stateany
yearstring
sizestring
has_mrzboolean
has_chipboolean
deprecatedboolean
confidencenumber
warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/idv/classify \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_62b56d3e2b9b454e9724",
  "latency_ms": 1506,
  "data": {
    "document_name": "Czech Republic - ePassport (2006)",
    "doc_type": "PASSPORT",
    "category": "Passport",
    "country": "CZE",
    "country_name": "Czech Republic",
    "state": null,
    "year": "2006",
    "size": "ID-3",
    "has_mrz": true,
    "has_chip": true,
    "deprecated": false,
    "confidence": 0.723872721195221
  },
  "warnings": [],
  "error": null
}

Document MRZ

POST/v1/idv/mrzdocuments.mrz

The MRZ read from the document: the raw lines, the parsed fields, and every check digit recomputed under ICAO 9303 (the same rule for every country).

**Returns**

- lines[]: the raw MRZ lines - format: TD1, TD2, TD3 or a visa - valid: true when every check digit holds and dates are real - expired: true when the expiry date has passed - check_digits: each check digit with its recomputed digit and ok/fail - print_quality: the MRZ print checks: check sums, contrast, format, position, stains, symbols - parsed: the named fields

**Limits**

- Returns an empty result with a warning when the document has no MRZ.

Price: 0.06 credits per successful call.

Body parameters

imagestringrequired

Document page with the MRZ. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

Returns

The check ran.

Show the DocumentMrzResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The answer of Document MRZ.

Show 8 fields
linesarray of strings
formatstring
validboolean
expiredboolean
problemsarray of objects
Show 2 fields
fieldstring
issuestring
check_digitsobject
Show 5 fields
doc_numberobject
Show 2 fields
digitstring
okboolean
birth_dateobject
Show 2 fields
digitstring
okboolean
expiryobject
Show 2 fields
digitstring
okboolean
personal_numberobject
Show 2 fields
digitstring
okboolean
compositeobject
Show 2 fields
digitstring
okboolean
print_qualityobject
Show 7 fields
check_sumsstring
contraststring
doc_formatstring
mrz_formatstring
print_positionstring
stainsstring
symbolsstring
parsedobject
Show 20 fields
Surname And Given Namesstring
RemainderTermstring
Date of Birthstring
Given Namesstring
Surnamestring
Date of Expirystring
Document Class Codestring
Issuing State Namestring
Final Checkdigitstring
Optional Data CheckDigitstring
Optional Datastring
Issuing State Codestring
Document Numberstring
Document Number Checkdigitstring
Nationality Codestring
Date of Birth Checkdigitstring
Sexstring
Date of Expiry Checkdigitstring
Personal Numberstring
MRZ Typestring
warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/idv/mrz \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_594cea1a392d46ac8725",
  "latency_ms": 1164,
  "data": {
    "lines": [
      "P<CZESPECIMEN<<VZOR<<<<<<<<<<<<<<<<<<<<<<<<<",
      "99006000<8CZE1102299F16090641152291111<<<<24"
    ],
    "format": "TD3",
    "valid": false,
    "expired": true,
    "problems": [
      {
        "field": "birth_date",
        "issue": "not a real date"
      }
    ],
    "check_digits": {
      "doc_number": {
        "digit": "8",
        "ok": true
      },
      "birth_date": {
        "digit": "9",
        "ok": true
      },
      "expiry": {
        "digit": "4",
        "ok": true
      },
      "personal_number": {
        "digit": "2",
        "ok": true
      },
      "composite": {
        "digit": "4",
        "ok": true
      }
    },
    "print_quality": {
      "check_sums": "pass",
      "contrast": "not_checked",
      "doc_format": "not_checked",
      "mrz_format": "not_checked",
      "print_position": "not_checked",
      "stains": "not_checked",
      "symbols": "not_checked"
    },
    "parsed": {
      "Surname And Given Names": "SPECIMEN VZOR",
      "RemainderTerm": "0",
      "Date of Birth": "110229",
      "Given Names": "VZOR",
      "Surname": "SPECIMEN",
      "Date of Expiry": "2016-09-06",
      "Document Class Code": "P",
      "Issuing State Name": "Czech Republic",
      "Final Checkdigit": "4",
      "Optional Data CheckDigit": "2",
      "Optional Data": "1152291111",
      "Issuing State Code": "CZE",
      "Document Number": "99006000",
      "Document Number Checkdigit": "8",
      "Nationality Code": "CZE",
      "Date of Birth Checkdigit": "9",
      "Sex": "F",
      "Date of Expiry Checkdigit": "4",
      "Personal Number": "1152291111",
      "MRZ Type": "ID-3"
    }
  },
  "warnings": [],
  "error": null
}

Document Portrait

POST/v1/idv/portraitdocuments.portrait

The portrait cut from the document, and a one-time reference you can pass straight to Face Verify or Face Match as image1.

**Returns**

- portrait_ref: a doc_ token for the portrait, reusable in a Face call - face_detected: whether a face was found on the document - faces_on_document: how many faces the page carries (a second is often the ghost image) - face_confidence: 0 to 1, how sure the face detection is - portrait: the portrait image, base64

**Limits**

- The reference token lasts about an hour.

Price: 0.02 credits per successful call.

Body parameters

imagestringrequired

Document. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

Returns

The check ran.

Show the DocumentPortraitResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The answer of Document Portrait.

Show 5 fields
portrait_refstring
face_detectedboolean
faces_on_documentnumber
face_confidencenumber
portraitstring
warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/idv/portrait \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_f6d88e9d8641426bad23",
  "latency_ms": 1165,
  "data": {
    "portrait_ref": "doc_Vb3Xq9LmT2rKw8Zp4nYc1sHd",
    "face_detected": true,
    "faces_on_document": 1,
    "face_confidence": 0.96,
    "portrait": "<base64 image>"
  },
  "warnings": [],
  "error": null
}

Document Authenticity

POST/v1/idv/authenticitydocuments.authenticity

The document's checks and the capture's image quality. Each item is pass, fail or not_checked, with one overall verdict.

**Returns**

- overall: the top-line verdict: pass, fail or not_checked - failed[]: every check that failed, expiry included - summary: how many checks passed, failed or were not checked - checks[]: each document check with its status - image_quality: glare, focus, resolution, screen capture, bounds, portrait

**Limits**

- Status values: pass, fail, not_checked. A specimen document can read as fail by design.

Price: 0.25 credits per successful call.

Body parameters

image_frontstringrequired

Document front. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

image_backstring

Document back. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

Returns

The check ran.

Show the DocumentAuthenticityResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The answer of Document Authenticity.

Show 5 fields
overallstring
failedarray of strings
summaryobject
Show 3 fields
passnumber
failnumber
not_checkednumber
checksarray of objects
Show 2 fields
namestring
statusstring
image_qualityobject
Show 6 fields
focusstring
glarestring
portraitstring
boundsstring
perspectivestring
resolutionstring
warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/idv/authenticity \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image_front": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_188d8bca755843808f92",
  "latency_ms": 1181,
  "data": {
    "overall": "fail",
    "failed": [
      "expiry",
      "mrz",
      "text"
    ],
    "summary": {
      "pass": 2,
      "fail": 3,
      "not_checked": 2
    },
    "checks": [
      {
        "name": "docType",
        "status": "pass"
      },
      {
        "name": "expiry",
        "status": "fail"
      },
      {
        "name": "imageQA",
        "status": "pass"
      },
      {
        "name": "mrz",
        "status": "fail"
      },
      {
        "name": "security",
        "status": "not_checked"
      },
      {
        "name": "text",
        "status": "fail"
      },
      {
        "name": "vds",
        "status": "not_checked"
      }
    ],
    "image_quality": {
      "focus": "pass",
      "glare": "pass",
      "portrait": "pass",
      "bounds": "pass",
      "perspective": "pass",
      "resolution": "pass"
    }
  },
  "warnings": [],
  "error": null
}

Document Fields

POST/v1/idv/fieldsdocuments.fields

Which fields were read, from which sources, whether each is valid, and whether the sources agree. Values are masked, so it is safe to log.

**Returns**

- summary: how many fields are valid, failed or not checked, and how many disagree between sources - fields[].label: the field name - fields[].sources: where it was read: MRZ, VISUAL, BARCODE - fields[].status: valid, fail or not_checked - fields[].sources_agree: whether the sources match - fields[].value_masked: the value, masked

**Limits**

- Raw values are never returned here; use Document Read for full values, behind access control.

Price: 0.02 credits per successful call.

Body parameters

imagestringrequired

Document. The image as base64 (JPEG or PNG). The SDKs also take a file path or the file bytes.

Returns

The check ran.

Show the DocumentFieldsResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The answer of Document Fields.

Show 4 fields
date_formatstring
countnumber
summaryobject
Show 4 fields
passnumber
failnumber
not_checkednumber
sources_disagreenumber
fieldsarray of objects
Show 6 fields
typenumber
labelstring
statusstring
sourcesarray of strings
sources_agreestring
value_maskedstring
warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/idv/fields \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_0a12c067aef84eabb429",
  "latency_ms": 1229,
  "data": {
    "date_format": "yyyy-MM-dd",
    "count": 31,
    "summary": {
      "pass": 13,
      "fail": 8,
      "not_checked": 10,
      "sources_disagree": 3
    },
    "fields": [
      {
        "type": 25,
        "label": "Surname And Given Names",
        "status": "fail",
        "sources": [
          "VISUAL"
        ],
        "sources_agree": "not_checked",
        "value_masked": "S••••••••••Ó"
      },
      {
        "type": 25,
        "label": "Surname And Given Names",
        "status": "fail",
        "sources": [
          "MRZ",
          "VISUAL"
        ],
        "sources_agree": "fail",
        "value_masked": "S••••••••••R"
      },
      {
        "type": 364,
        "label": "RemainderTerm",
        "status": "fail",
        "sources": [
          "MRZ",
          "VISUAL"
        ],
        "sources_agree": "pass",
        "value_masked": "•"
      },
      {
        "type": 8,
        "label": "Surname",
        "status": "fail",
        "sources": [
          "VISUAL"
        ],
        "sources_agree": "not_checked",
        "value_masked": "S••••••N"
      }
    ]
  },
  "warnings": [],
  "error": null
}

Proof of Address

POST/v1/poa/analyzeproofOfAddress.analyze

Reads a proof-of-address document (utility bill, bank statement, letter) and checks it against the name, address and country you expect, with a recency and integrity check.

**Returns**

- document: what it is, type, issue date - address: the extracted address, split into parts - verification.comparisons: name, address and country against what you expected - verification.recency: how old the document is vs the limit - verification.decision: pass, refer or fail, with reasons

**Limits**

- PDF, JPG or PNG, up to 10 MB.

Price: 0.4 credits per successful call.

Body parameters

document_tokenstringrequired

Address document. Encrypted in your browser; the API takes the token of the stored document. A document token from the vault.

filenamestring

File name.

expectedobject
Show 3 fields
namestring

Expected name.

addressstring

Expected address.

countrystring

Expected country. ISO alpha-2, for example RW.

Returns

The check ran.

Show the ProofOfAddressAnalyzeResponse fields
okbooleanrequired

true: the result is in data.

request_idstring
latency_msnumber

How long the check took, in milliseconds.

dataobjectrequired

The full result of Proof of Address: everything the check found. See the fields listed in the operation.

warningsarray of strings
errorany

Empty on success.

Errors

  • 400A field is missing or not valid.
  • 401No API key, or the key is not valid, expired or revoked.
  • 402Not enough credits for this call (insufficient_credits), or the key reached its spend cap. Nothing was charged.
  • 403The key may not call this API (permission, environment or IP allow-list). Nothing was charged.
  • 413The image or the request is too large.
  • 429Too many requests for this key: wait Retry-After seconds. Nothing was charged.
  • 5XXThe check could not run. Nothing was charged.
Request
curl -X POST https://verisec.dev/v1/poa/analyze \
  -H "Authorization: Bearer $VERISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "document_token": "doc_example"
  }'
Response · 200
{
  "ok": true,
  "request_id": "req_7c1e2a9b40d35f6e8a12",
  "latency_ms": 21400,
  "data": {
    "schema_version": "1.0",
    "file": {
      "kind": "image",
      "pages": 1,
      "pages_analyzed": 1,
      "sha256": "9f2c0b7e41a3d58c"
    },
    "document": {
      "is_proof_of_address": true,
      "what_it_is": "A water utility bill",
      "not_accepted_because": null,
      "type": "utility_bill",
      "title_as_printed": "Water bill",
      "country": "RW",
      "languages": [
        "en"
      ],
      "issue_date": "2026-09-02",
      "period_end": "2026-08-31"
    },
    "issuer": {
      "name": "Example Water Ltd",
      "type": "utility",
      "contact_as_printed": "+250 700 000 000"
    },
    "holder": {
      "names": [
        "Jane Example"
      ],
      "role": "account_holder",
      "national_id": {
        "scheme": null,
        "value_masked": null
      }
    },
    "address": {
      "role": "service_address",
      "raw": "KG 000 St, Kacyiru, Gasabo, Kigali",
      "street": "KG 000 St",
      "sector": "Kacyiru",
      "district": "Gasabo",
      "city": "Kigali",
      "country": "RW"
    },
    "account": {
      "account_number": "000123456",
      "references": [
        {
          "kind": "invoice",
          "value": "INV-0001"
        }
      ]
    },
    "amounts": {
      "amount_due": 12500,
      "currency": "RWF",
      "line_items": [
        {
          "label": "Water",
          "amount": 12500
        }
      ]
    },
    "forensics": {
      "integrity": {
        "edited_text_suspected": false,
        "totals_consistent": true,
        "pixel_check": "clean"
      },
      "capture": {
        "screen_photo": false,
        "photocopy": false,
        "page_complete": true
      }
    },
    "verification": {
      "comparisons": {
        "name_vs_id": {
          "status": "match",
          "score": null
        },
        "address_vs_declared": {
          "status": "match",
          "score": null
        },
        "country_vs_expected": {
          "status": "match",
          "score": null
        }
      },
      "recency": {
        "status": "pass",
        "days_old": 33,
        "max_days": 90,
        "date_used": "2026-09-02"
      },
      "document_type": {
        "status": "pass",
        "type": "utility_bill"
      },
      "integrity": {
        "status": "pass"
      },
      "authenticity_score": 92,
      "decision": "pass",
      "reasons": [
        {
          "code": "recent",
          "label": "Issued within 90 days",
          "tone": "good"
        }
      ]
    }
  },
  "warnings": [],
  "error": null
}