Document Comparison
Verify that 2D-DOC barcode data matches the source document content
The compare endpoint combines barcode verification with document text extraction to verify that the barcode data matches what's printed on the document. This detects fraudulent modifications to the visible document content.
Request
POST /2ddoc/compare
Authorization: Bearer oat_your_publishable_key
Content-Type: multipart/form-data
file: <PDF or image file>
forceOcr: true| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | PDF or image (pdf, png, jpg, jpeg, gif, webp, bmp, tiff), max 20MB |
forceOcr | string | No | "true" to force OCR even when the PDF has a text layer |
async | string | No | "true" to queue the comparison and answer 202 at once, like the Prefer: respond-async header. See asynchronous processing |
Comparison requires a detected 2D-DOC barcode. If no barcode is found, the response is { "found": false, "error": "..." } (same as scan). If the file's only code is not a 2D-DOC, the response is { "found": true, "notTwoDDoc": true, "verification": { ... }, "documents": [...] } with the failed verdict described in scan, and no comparison.
Response
The barcode result lives under verification (the full verify response), and the text comparison under comparison. There is no top-level success field.
These top-level fields hold the first 2D-DOC of the file. documents lists every 2D-DOC in page order, the first one included, each with its page, verification and comparison. When a PDF holds several codes, each one is compared with the text of its own page only. Each 2D-DOC counts as one verification.
{
"found": true,
"page": 1,
"verification": {
"status": "valid",
"verification": {
"SIGNATURE": [
{ "status": "✅", "check": "Signature is cryptographically valid" }
]
},
"document": {
"header": { "formatVersion": "v1", "version": "04", "caId": "FR04", "certificateId": "0001", "documentTypeCode": "01", "documentEmissionDate": "2026-01-15T00:00:00.000Z", "signatureDate": "2026-01-15T00:00:00.000Z", "signatureDateFormatted": "15/01/2026" },
"documentType": "Facture",
"fields": { "invoice": [ { "id": "10", "value": "MME/SPECIMEN/NATACHA", "rawValue": "MME/SPECIMEN/NATACHA", "truncated": false, "label": "Nom du bénéficiaire", "category": "invoice" } ] },
"data": { "beneficiary_name": "MME/SPECIMEN/NATACHA" }
},
"certificate": { "providerTradeName": "Certigna", "trusted": true }
},
"comparison": {
"success": true,
"overallConfidence": 0.95,
"fieldsMatched": 5,
"totalFields": 6,
"fields": [
{
"fieldId": "10",
"label": "Nom du bénéficiaire",
"expectedValue": "MME/SPECIMEN/NATACHA",
"found": true,
"confidence": 1.0,
"matchType": "exact",
"matchedText": "MME SPECIMEN NATACHA"
},
{
"fieldId": "22",
"label": "Ligne 4 adresse point de service",
"expectedValue": "145 AV DES SPECIMENS",
"found": true,
"confidence": 0.9,
"matchType": "normalized",
"matchedText": "145 Avenue des Spécimens"
}
],
"extraction": {
"method": "pdf-text",
"textLength": 4523,
"text": "FACTURE\nMME SPECIMEN NATACHA\n145 Avenue des Spécimens\n..."
}
},
"documents": [
{ "page": 1, "verification": { "…": "…" }, "comparison": { "…": "…" } }
]
}Comparison fields
| Field | Type | Description |
|---|---|---|
success | boolean | Whether text extraction and comparison ran successfully |
overallConfidence | number | Overall confidence score (0-1) |
fieldsMatched | number | Fields found with confidence at or above 0.7 |
totalFields | number | Total number of fields compared |
fields | array | Per-field comparison results |
extraction.method | string | pdf-text+ocr for a PDF with a text layer (the text layer and the OCR of the code's page, merged), ocr for a scan or a photo, pdf-text when the page could not be read by OCR |
extraction.textLength | number | Length of the extracted text |
extraction.text | string | Raw extracted text, useful for debugging OCR issues |
Confidence scores
Each field gets a confidence score based on how it was matched:
| Match type | Confidence | Description |
|---|---|---|
exact | 1.0 | Exact string match (including date/numeric format variations) |
normalized | 0.9-0.95 | Match after normalization (accents, abbreviations) |
partial | 0.5-0.85 | Partial word match (60%+ words found) |
not-found | 0.0 | Field not found in document text |
mismatch | 0.0 | The document prints another value right after the field's label; matchedText holds that value |
Overall confidence
The overallConfidence score reflects the quality of fields that were found on the document. Fields not found are treated as neutral; issuers often encode data in the barcode (country codes, internal references) that doesn't appear on the printed document, and this should not lower the trust score.
The scoring policy:
- All found fields match perfectly gives 100%: missing fields don't penalize the score
- Weak matches are very damaging: a field partially found but with low confidence (a potential mismatch) is amplified as a penalty. A wrong value is far more suspicious than a missing one
- A mismatch makes the score 0: when the page prints another value than the code right after a field's label (
Nombre de parts : 2,00against a signed1), the document was altered after it was issued, whatever else matches - Majority missing triggers a coverage penalty: if more than 70% of fields are absent and there are many fields, a penalty applies to catch "wrong document entirely" scenarios
Text extraction
The API uses two extraction methods:
- PDF text layer and OCR of the page (
pdf-text+ocr): the text layer is read, and the page holding the code is always read by OCR too. A forger can draw a value over the page or leave the genuine values in the text layer under a retouched page: the two readings are merged, and a value contradicted in either is amismatch - OCR (
ocr): for scanned documents or images, with multiple Tesseract passes
Use the forceOcr form field to skip the PDF text layer and run OCR anyway.
Matching strategies
French documents have format variations that the comparison handles:
| Strategy | Example |
|---|---|
| Date formats | 31072025 matches 31/07/2025 |
| Numeric spacing | 3009811657195 matches 30 09 811 657 195 |
| OCR corrections | 3O O9 corrected to 30 09 (O/0 confusion) |
| Accent normalization | Jérôme matches Jerome |
| Whole words only | 1 is not found in 19, 2025 or 1,5; a value under 3 characters counts only right after its label |
| Address abbreviations | AV matches Avenue, BD matches Boulevard |
Use cases
- KYC verification: verify that uploaded identity documents haven't been tampered with
- Tax document validation: confirm tax notice data before processing
- Fraud detection: flag documents where visible content doesn't match the barcode data