> This page is for version v2026-02-09 (default).
> For other versions, use one of these documentation indexes:
> - v2026-02-09 (default): https://docs.extend.ai/2026-02-09/llms.txt
> - v2025-04-21: https://docs.extend.ai/2025-04-21/llms.txt
> - v2024-12-23: https://docs.extend.ai/2024-12-23/llms.txt

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extend.ai/llms.txt
> Use this file to discover all available pages before exploring further.
>
> ## API version
> The current API version is `2026-02-09`, served at the site root (no version prefix in URLs).
> If this page URL contains `/2025-04-21/` or `/2024-12-23/`, you are reading an older API version.
> Prefer the current docs at https://docs.extend.ai/llms.txt unless the user explicitly needs that older version.
> Do not treat older-version pages as the source of truth for new integrations.

# Verify Overview

> Verify checks a single PDF, JPEG, PNG, or WebP for evidence of tampering, forgery, or AI generation and returns a risk level, detected signals, and locatable evidence. Covers POST /verify, verifyRuns.create, and verifyRuns.retrieve in Python, TypeScript, Java, Go, and cURL.

> **Note**
>
> **Verify is in beta.** The endpoints return `404 NOT_FOUND` unless Verify is enabled for your organization. Contact [support@extend.ai](mailto:support@extend.ai) to request access. Verify runs are not metered during the beta, so `usage` is `null` on every run.

**Verify** checks one document for evidence of fraud: whole-document fabrication, localized edits, and suspicious file internals. You submit a PDF, JPEG, PNG, or WebP and get back a document-level `riskLevel` (`LOW`, `MEDIUM`, or `HIGH`), a plain-language `summary`, and the detected `signals`, each with typed, locatable `evidence` that a reviewer can check against the file. Use it to screen bank statements, pay stubs, invoices, identity credentials, and any other document a person could alter before uploading.

Verify analyzes the exact bytes you submit. It combines deterministic checks of file metadata and PDF internals (signatures, revision history, hidden layers, fonts) with detection models for image manipulation and AI generation, then an assessment model grades each detection in the context of the whole document. It does not perform identity verification, compare documents to each other, or claim that a document is genuine: a `LOW` risk level means the checks that ran found nothing, not that the document is authentic.

## Quick start

We'll verify a bank statement that has been tampered with. It is the sample statement from the Parsing quick start, digitally signed with a "no changes allowed" certification signature, then edited after signing to delete one row (a \$15.00 incoming wire fee) from the Fees and Other Withdrawals table on page 6, leaving the printed section total unchanged. For this quick start we've uploaded the file [here.](https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf)

Grab a key from the [Developers](https://dashboard.extend.ai/developers) page and store it as the `EXTEND_API_KEY` environment variable. If you're using an SDK, see the [installation instructions](/sdks).

```bash
export EXTEND_API_KEY="your_api_key_here"
```

The `/verify` endpoint takes a `file` and an optional `config`. With no `config`, it uses the default `verify_agentic` engine, which parses the document and runs every supported check.

#### Python

```python
from extend_ai import Extend

client = Extend()

run = client.verify(
    file={
        "url": "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf",
    }
)

print(run.output.risk_level, run.output.summary)
```

#### TypeScript

```typescript
import { ExtendClient } from "extend-ai";

const client = new ExtendClient();

const run = await client.verify({
  file: {
    url: "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf",
  },
});

console.log(run.output?.riskLevel, run.output?.summary);
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.requests.VerifyRequest;
import ai.extend.types.FileFromUrl;
import ai.extend.types.VerifyRequestFile;
import ai.extend.types.VerifyRun;

ExtendClient client = ExtendClient.builder().build();

VerifyRun run = client.verify(VerifyRequest.builder()
    .file(VerifyRequestFile.of(FileFromUrl.builder()
        .url("https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf")
        .build()))
    .build());

run.getOutput().ifPresent(output ->
    System.out.println(output.getRiskLevel() + " " + output.getSummary()));
```

#### Go

```go
package main

import (
	"context"
	"fmt"
	"log"

	extend "github.com/extend-hq/extend-go-sdk"
	client "github.com/extend-hq/extend-go-sdk/client"
)

func main() {
	c := client.NewClient()

	run, err := c.Verify(context.TODO(), &extend.VerifyRequest{
		File: &extend.VerifyRequestFile{
			FileFromURL: &extend.FileFromURL{
				URL: "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf",
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(run.Output.RiskLevel, run.Output.Summary)
}
```

#### cURL

```bash
curl -X POST "https://api.extend.ai/verify" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": {
      "url": "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf"
    }
  }'
```

> **Tip**
>
> Want to verify your own document? [Upload it](/api-reference/endpoints/file/upload-file) first, then pass the returned file `id` instead of a `url`. Verify accepts PDF, JPEG, PNG, and WebP files.
>
> #### Python
>
> ```python
> with open("bank_statement.pdf", "rb") as f:
>     uploaded = client.files.upload(file=f)
>
> run = client.verify(file={"id": uploaded.id})
> ```
>
> #### TypeScript
>
> ```typescript
> import { createReadStream } from "fs";
>
> const uploaded = await client.files.upload(createReadStream("bank_statement.pdf"), {});
>
> const run = await client.verify({ file: { id: uploaded.id } });
> ```
>
> #### Java
>
> ```java
> import ai.extend.requests.FilesUploadRequest;
> import ai.extend.types.File;
> import ai.extend.types.FileFromId;
> import ai.extend.types.VerifyRequestFile;
>
> File uploaded = client.files().upload(
>     new java.io.File("bank_statement.pdf"),
>     FilesUploadRequest.builder().build());
>
> VerifyRun run = client.verify(VerifyRequest.builder()
>     .file(VerifyRequestFile.of(FileFromId.builder().id(uploaded.getId()).build()))
>     .build());
> ```
>
> #### Go
>
> ```go
> f, err := os.Open("bank_statement.pdf")
> if err != nil {
> 	log.Fatal(err)
> }
> defer f.Close()
>
> uploaded, err := c.Files.Upload(context.TODO(), f, &extend.FilesUploadRequest{})
> if err != nil {
> 	log.Fatal(err)
> }
>
> run, err := c.Verify(context.TODO(), &extend.VerifyRequest{
> 	File: &extend.VerifyRequestFile{
> 		FileFromID: &extend.FileFromID{ID: uploaded.ID},
> 	},
> })
> ```
>
> #### cURL
>
> ```bash
> # Upload the file to get an id
> curl -X POST https://api.extend.ai/files/upload \
>   -H "Authorization: Bearer $EXTEND_API_KEY" \
>   -H "x-extend-api-version: 2026-02-09" \
>   -F "file=@bank_statement.pdf"
>
> # Then verify using the returned id
> curl -X POST https://api.extend.ai/verify \
>   -H "Authorization: Bearer $EXTEND_API_KEY" \
>   -H "x-extend-api-version: 2026-02-09" \
>   -H "Content-Type: application/json" \
>   -d '{ "file": { "id": "file_xK9mLPqRtN3vS8wF5hB2cQ" } }'
> ```

### Example response

This is the response the run above returns, with repetitive evidence locations trimmed. Verify found the edit three different ways: the file's revision history shows page 6 replaced after the signature, the signature check shows that the certification forbade any change, and the content check shows the fee rows no longer add up to the printed total. A fourth signal, about font subsets and clipped overflow text, was detected but graded `LOW` because the assessment recognised it as ordinary template behaviour. The sample statement is also missing page 2 of 10, which the content check reports as a page-number gap.

```json
{
  "object": "verify_run",
  "id": "vr_AZMlpuOhBFhHc2NC6g4qd",
  "file": {
    "object": "file",
    "id": "file_JspRZAP5B7G3AQAfGJlpW",
    "name": "bank_statement_tampered.pdf",
    "type": "PDF",
    "parentFileId": null,
    "metadata": { "pageCount": 7 },
    "dataRetention": { "mode": "workspace_default", "status": "available" },
    "createdAt": "2026-10-05T13:34:20.773Z",
    "updatedAt": "2026-10-05T13:34:30.523Z"
  },
  "status": "PROCESSED",
  "output": {
    "riskLevel": "HIGH",
    "summary": "This statement shows strong signs of material alteration: page 6 was replaced after a no-changes-allowed digital signature, and that same page contains a $171.00 mismatch in the Fees and Other Withdrawals section with an apparent missing row. The submission also omits page 2 of the 10-page statement, so the missing pages should be obtained before relying on it.",
    "signals": [
      {
        "id": "vs_oqPSnnYrbSsI0ozvyohto",
        "type": "REVISION_AND_PRODUCTION_HISTORY",
        "riskAssessment": { "type": "LEVEL", "level": "HIGH" },
        "summary": "The PDF was revised after signing, and the later revision replaced the content of page 6 rather than making only routine metadata updates.",
        "evidence": [
          {
            "evidenceId": "meta.revision_history:001",
            "type": "history",
            "data": {
              "events": [
                { "code": "PDF_REVISION_CREATED", "locationIds": ["rev_0"] },
                { "code": "PDF_REVISION_APPENDED", "locationIds": ["rev_1"] },
                { "code": "METADATA_CHANGED", "locationIds": ["rev_1", "obj_292_r1"] },
                {
                  "code": "SIGNATURE_APPLIED",
                  "locationIds": ["sig_296"],
                  "timestamp": "D:20261005092239-04'00'",
                  "actor": "Extend Verify Test Signer",
                  "values": [
                    { "name": "permitted_changes", "raw": "NONE" },
                    { "name": "field_name", "raw": "Signature1" }
                  ]
                },
                { "code": "PDF_REVISION_APPENDED", "locationIds": ["rev_2"] },
                { "code": "METADATA_CHANGED", "locationIds": ["rev_2", "obj_292_r2"] },
                { "code": "PAGE_CONTENT_REPLACED", "locationIds": ["rev_2", "obj_49_r2", "obj_49_r1"] }
              ]
            },
            "locations": [
              { "locationId": "rev_0", "byteRange": { "offset": 0, "length": 482736 } },
              { "locationId": "rev_1", "byteRange": { "offset": 482736, "length": 6701 } },
              { "locationId": "sig_296", "objectRefs": ["296 0 R"], "byteRange": { "offset": 483489, "length": 5609 } },
              { "locationId": "rev_2", "byteRange": { "offset": 489437, "length": 3917 } },
              { "locationId": "obj_49_r2", "page": { "number": 6 }, "objectRefs": ["49 0 R"], "byteRange": { "offset": 489437, "length": 3353 } },
              { "locationId": "obj_49_r1", "page": { "number": 6 }, "objectRefs": ["49 0 R"], "byteRange": { "offset": 26639, "length": 3981 } }
              /* 2 more locations: the metadata object in each revision */
            ]
          }
        ]
      },
      {
        "id": "vs_JZ6QC3TYP3SZEdQjhpScj",
        "type": "DIGITAL_SIGNATURE_INTEGRITY",
        "riskAssessment": { "type": "LEVEL", "level": "HIGH" },
        "summary": "A no-changes-allowed signature was followed by later edits that replaced a statement page. The signature certificate is self-signed, which is not suspicious on its own but offers limited identity assurance.",
        "evidence": [
          {
            "evidenceId": "meta.modified_after_signing:001",
            "type": "history",
            "data": {
              "events": [
                {
                  "code": "SIGNATURE_APPLIED",
                  "locationIds": ["sig_296"],
                  "timestamp": "D:20261005092239-04'00'",
                  "actor": "Extend Verify Test Signer",
                  "values": [
                    { "name": "permitted_changes", "raw": "NONE" },
                    { "name": "field_name", "raw": "Signature1" }
                  ]
                },
                { "code": "METADATA_CHANGED", "locationIds": ["rev_2", "obj_292_r2"] },
                { "code": "PAGE_CONTENT_REPLACED", "locationIds": ["rev_2", "obj_49_r2", "obj_49_r1"] }
              ]
            },
            "locations": [
              { "locationId": "sig_296", "objectRefs": ["296 0 R"], "byteRange": { "offset": 483489, "length": 5609 } },
              { "locationId": "rev_2", "byteRange": { "offset": 489437, "length": 3917 } },
              { "locationId": "obj_49_r2", "page": { "number": 6 }, "objectRefs": ["49 0 R"], "byteRange": { "offset": 489437, "length": 3353 } },
              { "locationId": "obj_49_r1", "page": { "number": 6 }, "objectRefs": ["49 0 R"], "byteRange": { "offset": 26639, "length": 3981 } }
              /* 1 more location: the metadata object in the final revision */
            ]
          },
          {
            "evidenceId": "meta.certificate_or_timestamp_invalid:001",
            "type": "validation",
            "data": {
              "findings": [
                {
                  "code": "SELF_SIGNED_SIGNER",
                  "values": [
                    { "name": "certificate", "raw": "signer, Extend Verify Test Signer", "locationIds": ["sig_296_contents"] }
                  ]
                }
              ]
            },
            "locations": [
              { "locationId": "sig_296", "objectRefs": ["296 0 R"], "byteRange": { "offset": 483489, "length": 5609 } },
              { "locationId": "sig_296_contents", "byteRange": { "offset": 483502, "length": 5200 } }
            ]
          }
        ]
      },
      {
        "id": "vs_wm9njEdG4mEFFk8n1k5wR",
        "type": "PDF_LAYERS_TEXT_AND_FORMS",
        "riskAssessment": { "type": "LEVEL", "level": "LOW" },
        "summary": "The font-subset differences and clipped text match common PDF template and layout behavior, including repeated overflow text at the page edge, and do not by themselves indicate fraud.",
        "evidence": [
          /* 6 structural_pattern items: two SECOND_SUBSET_OF_SAME_FACE font findings and four TEXT_CLIPPED_AWAY findings for a reference number that overflows the page edge, each with boundingBox locations */
        ]
      },
      {
        "id": "vs_TbroCORqGt8mudBAfSJQS",
        "type": "FINANCIAL_RECONCILIATION_AND_SEQUENCE_CONTINUITY",
        "riskAssessment": { "type": "LEVEL", "level": "HIGH" },
        "summary": "The Fees and Other Withdrawals detail does not add up to its printed total, with a $171.00 gap and a blank row where an entry appears missing. The submitted statement also skips page 2 of 10, so the missing pages should be obtained and reviewed.",
        "evidence": [
          {
            "evidenceId": "content.arithmetic_or_ledger_mismatch:001",
            "type": "validation",
            "data": {
              "findings": [
                {
                  "code": "STATED_TOTAL_DOES_NOT_MATCH_COMPONENTS",
                  "values": [
                    {
                      "name": "finding",
                      "raw": "The Fees and Other Withdrawals rows add up to $7,049.00, but the section total says $7,220.00, a difference of $171.00. A blank row sits where an entry should be.",
                      "locationIds": ["loc_1", "loc_2"]
                    }
                  ]
                }
              ]
            },
            "locations": [
              { "locationId": "loc_1", "page": { "number": 6, "width": 612, "height": 792 }, "boundingBox": { "left": 496.476, "top": 283.356, "right": 532.714, "bottom": 504.007 } },
              { "locationId": "loc_2", "page": { "number": 6, "width": 612, "height": 792 }, "boundingBox": { "left": 490.903, "top": 536.062, "right": 531.914, "bottom": 546.257 } }
            ]
          },
          {
            "evidenceId": "content.sequence_or_continuity_gap:001",
            "type": "validation",
            "data": {
              "findings": [
                {
                  "code": "PAGE_NUMBER_GAP",
                  "values": [
                    {
                      "name": "finding",
                      "raw": "The page footers go from \"Page 1 of 10\" straight to \"Page 3 of 10\", so page 2 of the 10-page statement is missing.",
                      "locationIds": ["loc_1", "loc_2"]
                    }
                  ]
                }
              ]
            },
            "locations": [
              { "locationId": "loc_1", "page": { "number": 1, "width": 612, "height": 792 }, "boundingBox": { "left": 464.782, "top": 762.372, "right": 502.827, "bottom": 770.019 } },
              { "locationId": "loc_2", "page": { "number": 2, "width": 612, "height": 792 }, "boundingBox": { "left": 464.724, "top": 762.358, "right": 502.43, "bottom": 770.011 } }
            ]
          }
        ]
      }
    ]
  },
  "failureReason": null,
  "failureMessage": null,
  "metadata": null,
  "metrics": { "pageCount": 7, "processingTimeMs": 85681 },
  "config": {
    "engine": "verify_agentic",
    "engineVersion": "0.1.0",
    "advancedOptions": { "requireParsing": true }
  },
  "usage": null,
  "source": "API",
  "sourceId": "apireq_Ui6c6cyBVw7nWKqUCJKGt",
  "createdAt": "2026-10-05T13:34:21.108Z",
  "updatedAt": "2026-10-05T13:35:46.991Z"
}
```

A document with nothing detected returns `"riskLevel": "LOW"`, `"summary": "No fraud signals were detected."`, and an empty `signals` array.

### Key fields

| Field                      | What it contains                                                                                                                                                |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output.riskLevel`         | Document-level risk: `LOW`, `MEDIUM`, or `HIGH`. The highest signal-level assessment, or `LOW` when nothing was detected.                                       |
| `output.summary`           | A plain-language explanation of the findings, written for a reviewer.                                                                                           |
| `output.signals`           | Detected [Public Signals](/verify/signals) only. Nothing is returned for checks that found nothing, could not run, or do not apply.                             |
| `signals[].type`           | Which signal category was detected, such as `DIGITAL_SIGNATURE_INTEGRITY`.                                                                                      |
| `signals[].riskAssessment` | The risk attributed to that signal: `{ "type": "LEVEL", "level": "HIGH" }`.                                                                                     |
| `signals[].evidence`       | Typed, immutable observations behind the signal, each with `locations` in the submitted file (page number, bounding box, PDF object references, or byte range). |
| `config`                   | The configuration that ran, with `engineVersion` resolved to a concrete release and `advancedOptions.requireParsing` saying whether the document was parsed.    |
| `status`                   | `PROCESSING`, `PROCESSED`, `FAILED`, or `CANCELLED`.                                                                                                            |

For every field, see [Response Format](/verify/response-format). For full request/response details, see the [Verify File API reference](/api-reference/endpoints/verify/verify-file).

### Use the output

Route on `riskLevel`, then surface the signals and their summaries to whoever reviews the document.

#### Python

```python
if run.output.risk_level == "HIGH":
    send_to_fraud_review(run.id)

for signal in run.output.signals:
    print(f"{signal.type} ({signal.risk_assessment.level}): {signal.summary}")
    for item in signal.evidence:
        print("  evidence:", item.evidence_id, item.type)
```

#### TypeScript

```typescript
if (run.output?.riskLevel === "HIGH") {
  await sendToFraudReview(run.id);
}

for (const signal of run.output?.signals ?? []) {
  console.log(`${signal.type} (${signal.riskAssessment.level}): ${signal.summary}`);
  for (const item of signal.evidence) {
    console.log("  evidence:", item.evidenceId, item.type);
  }
}
```

#### Java

```java
var output = run.getOutput().get();

if (output.getRiskLevel() == VerifyRiskLevel.HIGH) {
    sendToFraudReview(run.getId());
}

for (var signal : output.getSignals()) {
    System.out.println(signal.getType() + ": " + signal.getSummary());
    for (var item : signal.getEvidence()) {
        System.out.println("  evidence: " + item);
    }
}
```

#### Go

```go
if run.Output.RiskLevel == extend.VerifyRiskLevelHigh {
	sendToFraudReview(run.ID)
}

for _, signal := range run.Output.Signals {
	fmt.Printf("%s: %s\n", signal.Type, signal.Summary)
	for _, item := range signal.Evidence {
		fmt.Println("  evidence:", item)
	}
}
```

## Sync vs async

The example above calls the synchronous `/verify` endpoint, which times out after 5 minutes on both engines. A timed-out request usually fails with `504 Gateway Timeout` and no `runId`, while the run keeps processing in the background. `verify_agentic` runs often take longer than that. For production workloads, use the asynchronous `/verify_runs` endpoint: it returns a run in the `PROCESSING` state immediately, and you poll `GET /verify_runs/{id}` until `status` is `PROCESSED`, `FAILED`, or `CANCELLED`. `GET /verify_runs` lists past runs with their risk levels, and `DELETE /verify_runs/{id}` removes a run and the data derived from its document.

> **Note**
>
> Webhook events for verify runs are not available yet, and the SDKs do not yet include a `create_and_poll` / `createAndPoll` helper for verify runs. Poll the run as shown below. See [Async Processing](/general/async-processing) for the general pattern.

#### Python

```python
import time

run = client.verify_runs.create(
    file={"url": "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf"}
)

while run.status == "PROCESSING":
    time.sleep(2)
    run = client.verify_runs.retrieve(run.id)

if run.status == "PROCESSED":
    print(run.output.risk_level, run.output.summary)
else:
    print(run.status, run.failure_reason, run.failure_message)
```

#### TypeScript

```typescript
let run = await client.verifyRuns.create({
  file: { url: "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf" },
});

while (run.status === "PROCESSING") {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  run = await client.verifyRuns.retrieve(run.id);
}

if (run.status === "PROCESSED") {
  console.log(run.output?.riskLevel, run.output?.summary);
} else {
  console.log(run.status, run.failureReason, run.failureMessage);
}
```

#### Java

```java
import ai.extend.requests.VerifyRunsCreateRequest;
import ai.extend.types.VerifyRunsCreateRequestFile;
import ai.extend.types.VerifyRunStatus;

VerifyRun run = client.verifyRuns().create(VerifyRunsCreateRequest.builder()
    .file(VerifyRunsCreateRequestFile.of(FileFromUrl.builder()
        .url("https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf")
        .build()))
    .build());

while (run.getStatus() == VerifyRunStatus.PROCESSING) {
    Thread.sleep(2000);
    run = client.verifyRuns().retrieve(run.getId());
}

if (run.getStatus() == VerifyRunStatus.PROCESSED) {
    var output = run.getOutput().get();
    System.out.println(output.getRiskLevel() + " " + output.getSummary());
} else {
    System.out.println(run.getStatus() + " " + run.getFailureMessage().orElse(""));
}
```

#### Go

```go
run, err := c.VerifyRuns.Create(ctx, &extend.VerifyRunsCreateRequest{
	File: &extend.VerifyRunsCreateRequestFile{
		FileFromURL: &extend.FileFromURL{
			URL: "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_tampered.pdf",
		},
	},
})
if err != nil {
	log.Fatal(err)
}

for run.Status == extend.VerifyRunStatusProcessing {
	time.Sleep(2 * time.Second)
	if run, err = c.VerifyRuns.Retrieve(ctx, run.ID, &extend.VerifyRunsRetrieveRequest{}); err != nil {
		log.Fatal(err)
	}
}

if run.Status == extend.VerifyRunStatusProcessed {
	fmt.Println(run.Output.RiskLevel, run.Output.Summary)
} else if run.FailureMessage != nil {
	fmt.Println(run.Status, *run.FailureMessage)
}
```

## What Verify checks

Findings are reported as **Public Signal** categories, a stable enum. The current engine release runs checks in these categories:

| Signal                                             | Detects                                                                                                                                                                                                                                    |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `FILE_FORMAT_AND_PDF_STRUCTURE`                    | Files disguised as another type, broken or rebuilt PDF structure, content after the end of the file, and files that read differently to different PDF software.                                                                            |
| `DIGITAL_SIGNATURE_INTEGRITY`                      | Content changed after signing, signatures that do not match the file they are attached to, and signer certificates or timestamps that did not hold up when the signature was made.                                                         |
| `PROVENANCE_AND_ISSUER_MARKERS`                    | Content credentials that fail to verify, and provenance watermarks whose manifest has been stripped.                                                                                                                                       |
| `REVISION_AND_PRODUCTION_HISTORY`                  | Files re-saved after issuance, backdated timestamps, interactive image editors in the production chain, and scanned pages encoded differently from the rest.                                                                               |
| `CAPTURE_CONVERSION_AND_LAUNDERING`                | Annotation layers that hide or change what a reader sees.                                                                                                                                                                                  |
| `PDF_LAYERS_TEXT_AND_FORMS`                        | Values masked and retyped, image patches pasted over a scan, hidden text, form values that differ from what the form displays, values set in a font the rest of the line does not use, and an OCR text layer that disagrees with the page. |
| `IMAGE_COMPOSITING_AND_RECONSTRUCTION`             | Regions spliced, cloned, erased, or painted over, detected by an image manipulation model.                                                                                                                                                 |
| `AI_GENERATED_CONTENT`                             | Generator watermarks, content credentials that assert generation, and pages or regions a detection model judges to be synthetic.                                                                                                           |
| `CROSS_FIELD_AND_SEMANTIC_CONSISTENCY`             | Dates that contradict each other, and names, addresses, identifiers, or issuers stated differently in different places. Found by a content agent reading the parsed document; `verify_agentic` only.                                       |
| `FINANCIAL_RECONCILIATION_AND_SEQUENCE_CONTINUITY` | Totals that do not add up, running balances that do not reconcile, and gaps in page numbers, statement periods, or transaction order. `verify_agentic` only.                                                                               |

## Configuration

The quick start uses default settings. To control a run, pass a `config` object alongside `file`. For the full reference, see [Configuration](/verify/configuration).

### Engine

```json
{ "config": { "engine": "verify_light" } }
```

| Engine           | When to use                                                                                                                                                                                                                                                                       |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verify_agentic` | Parses the file and runs every check, including the checks that read the parse, and leverages an agent harness optimized for tamper detection. Slower and more expensive, but the most performant option for detecting difficult cases of document fraud.                         |
| `verify_light`   | Faster checks that do not require parsing, but will use parsed results if the file has already been parsed in Extend. Covers all checks except the agentic checks around content and OCR. Add `"advancedOptions": { "requireParsing": true }` to include the parse-backed checks. |

### Parsing on the light engine

`verify_light` skips the parse for speed. Set `advancedOptions.requireParsing` to add the checks that read the parse, such as the comparison of a scan's hidden text layer against the page. `verify_agentic` always parses and rejects `false`.

```json
{ "config": { "engine": "verify_light", "advancedOptions": { "requireParsing": true } } }
```

## Supported inputs

* **File types:** PDF, JPEG, PNG, and WebP. Other types fail with `FILE_TYPE_NOT_SUPPORTED`. A file that claims to be a PDF but is not still runs, because the mismatch is itself a signal.
* **Input methods:** a public `url` or an Extend file `id`.
* **Data retention:** a verify run inherits the retention mode of its file. For zero-retention files the result is delivered once, in the sync response or the first `GET`, and purged afterwards, so store what you need from that response. `DELETE /verify_runs/{id}` removes any run's derived data on demand.

## Via Extend Studio

Studio includes a Verify playground where you can run a file, browse past runs, and inspect each signal's evidence highlighted on the document. Open [Verify in Studio](https://dashboard.extend.ai/studio/verify/playground) (it is visible only to organizations enrolled in the beta). Studio runs use the same engine, configuration options, and result shape as the API.

## What Verify is not

* **Not identity verification.** Verify says nothing about whether the person submitting a document is who they claim to be, and it never links a document to a person.
* **Not cross-document analysis.** Each run looks at one file. It does not compare submissions to each other or to previously seen documents.
* **Not a certificate of authenticity.** Signals are returned only when detected. An empty `signals` array means the checks that ran found nothing; it does not mean every check applied, or that the document is genuine.

---

## Next steps

#### [Configuration](/verify/configuration)

Engine, engine version, parsing on the light engine, and advanced options.

#### [Signals](/verify/signals)

Every Public Signal, which engine runs it, what it detects, and the evidence it returns.

#### [Response Format](/verify/response-format)

The verify run, signals, risk assessment, and the six evidence types with their locations.

#### [Error Handling](/verify/error-handling)

Failure reasons, HTTP status codes, and sync vs async error patterns.