Document Forensics
Document forensics detects image tampering — spliced photos, clone-stamped
regions, recompression artifacts, typography mismatches — that a plain
"is this a document?" check would miss. It runs automatically as part of the
document check and its result is available in the verification's checks_results
under forensics.
Checks
| Check | What it detects |
|---|---|
ela_splice | Error-level-analysis anomalies indicating a spliced/pasted region (e.g. a swapped portrait) |
copy_move | Clone-stamped regions (the same patch duplicated within the image) |
noise_residual | Recompression / low-energy noise anomalies, including subtle low-contrast tampering |
block_artifacts | JPEG block-grid inconsistencies from re-saving an edited image |
portrait_seam | Hard edges around the portrait photo — a pasted-in or replaced face |
typography | Font/typography inconsistencies in printed fields (MRZ/VIZ) versus the rest of the document |
Result structure
{
"forensics": {
"passed": false,
"score": 0.42,
"checks": [
{ "name": "ela_splice", "passed": false, "score": 0.31, "reason": "splice artifact near portrait" },
{ "name": "copy_move", "passed": true, "score": 0.92, "reason": "no duplicated regions" },
{ "name": "noise_residual", "passed": true, "score": 0.88, "reason": "noise consistent" },
{ "name": "block_artifacts", "passed": true, "score": 0.85, "reason": "consistent block grid" },
{ "name": "portrait_seam", "passed": false, "score": 0.35, "reason": "hard edge around portrait" },
{ "name": "typography", "passed": true, "score": 0.90, "reason": "fonts consistent" }
]
}
}
| Field | Type | Description |
|---|---|---|
passed | boolean | Overall forensics outcome (score >= threshold) |
score | float | Overall tamper score (0.0–1.0) |
checks[].name | string | Check identifier |
checks[].passed | boolean | Per-check outcome |
checks[].score | float | Per-check score |
checks[].reason | string | Human-readable explanation |
Threshold
The overall pass threshold is configurable via DOCUMENT_FORENSICS_THRESHOLD
(default 0.5). Lowering it makes the check stricter.
Multispectral checks
When you have multi-spectral captures (UV/IR), run the explicit check:
POST /verifications/{id}/documents/{doc_id}/multispectral
Send a multipart body with the available channels: visible (required), uv,
ir, coaxial.
curl -X POST https://api.dev.valydar.com/verifications/{id}/documents/{doc_id}/multispectral \
-H "Authorization: Bearer YOUR_API_KEY" \
-F visible=@document.jpg \
-F uv=@document_uv.jpg \
-F ir=@document_ir.jpg
Response:
{
"passed": true,
"checks": [
{ "name": "uv_fluorescence", "passed": true, "score": 0.9, "reason": "..." },
{ "name": "ir_absorption", "passed": true, "score": 0.88, "reason": "..." }
]
}
Results are merged into the verification under checks_results.security_features.
Multispectral requires the liveness:run permission.
When to use it
Forensics runs by default on document upload — no extra call needed. Call the multispectral endpoint only when you also capture UV/IR channels; it adds security-feature verification (hologram/fluorescent ink behaviour) on top of the image-forensics checks.