> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parcha.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Document Verification

> Complete guide to document verification with Parcha API

This guide covers how to verify business documents using the Parcha API. We'll use **incorporation document verification** as our canonical example to demonstrate the three main approaches.

## Choosing the Right Approach

Parcha offers three ways to verify documents, each optimized for different use cases:

```mermaid theme={null}
flowchart TD
    Start{Document Verification Need} --> Q1{Part of a full<br/>KYB/KYC review?}
    Q1 -->|Yes| Agent[1. Full Agent Job<br/>startKYBAgentJob]
    Q1 -->|No| Q2{Quick type check<br/>or thorough verification?}
    Q2 -->|Quick check| PreVal[2. Prevalidation<br/>runCheck with flags disabled]
    Q2 -->|Thorough| FullVal[3. Full Verification<br/>runCheck with flags enabled]

    style Start fill:#5D5FEF,stroke:#333,stroke-width:2px,color:#fff
    style Agent fill:#10b981,stroke:#333,stroke-width:2px,color:#fff
    style PreVal fill:#f59e0b,stroke:#333,stroke-width:2px,color:#fff
    style FullVal fill:#6366f1,stroke:#333,stroke-width:2px,color:#fff
```

### Quick Comparison

| Approach                 | Endpoint           | Speed    | Use Case                                                                       |
| ------------------------ | ------------------ | -------- | ------------------------------------------------------------------------------ |
| **1. Full Agent Job**    | `startKYBAgentJob` | Varies   | Document verification as part of comprehensive KYB review with multiple checks |
| **2. Prevalidation**     | `runCheck`         | Fast     | Quick document type validation before full review                              |
| **3. Full Verification** | `runCheck`         | Thorough | Complete verification with visual analysis and fraud detection                 |

***

## Incorporation Document Example

We'll demonstrate all three approaches using incorporation document verification. The same patterns apply to other document types (proof of address, EIN documents, etc.).

### Sample Business Data

```python theme={null}
business_data = {
    "registered_business_name": "Example Business Inc.",
    "incorporation_date": "2023-01-01",
    "address_of_incorporation": {
        "street_1": "123 Main St",
        "city": "Wilmington",
        "state": "DE",
        "country_code": "US",
        "postal_code": "19801"
    }
}
```

***

## Approach 1: Full Agent Job

<Card title="Best for: Comprehensive compliance reviews" icon="layer-group">
  Use Agent Jobs when you need to run **multiple checks** together as part of a unified compliance workflow with custom pass/fail criteria.
</Card>

**When to use:**

* Running a full KYB review with multiple checks (web presence + sanctions + document verification)
* You have an agent configured with specific pass/fail logic
* You need unified case view and risk scoring across all checks

**How it works:**

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant API
    participant Agent
    participant Checks
    
    Client->>API: POST /startKYBAgentJob
    API-->>Client: job_id (immediate)
    Agent->>Checks: Web Presence Check
    Agent->>Checks: Sanctions Screening
    Agent->>Checks: Incorporation Doc Verification
    Checks-->>Agent: Results
    Agent->>Agent: Apply pass/fail logic
    Agent-->>Client: Webhook notification
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.parcha.ai/api/v1/startKYBAgentJob' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "agent_key": "your-kyb-agent-key",
    "kyb_schema": {
      "id": "case-001",
      "self_attested_data": {
        "registered_business_name": "Example Business Inc.",
        "website": "https://example.com",
        "incorporation_date": "2023-01-01",
        "address_of_incorporation": {
          "street_1": "123 Main St",
          "city": "Wilmington",
          "state": "DE",
          "country_code": "US",
          "postal_code": "19801"
        },
        "incorporation_documents": [
          {
            "file_name": "certificate_of_incorporation.pdf",
            "b64_document": "BASE64_ENCODED_DOCUMENT"
          }
        ]
      }
    },
    "webhook_url": "https://your-webhook.com/callback"
  }'
  ```

  ```python Python theme={null}
  import requests
  import base64

  api_key = 'YOUR_API_KEY'
  url = 'https://api.parcha.ai/api/v1/startKYBAgentJob'

  with open('certificate_of_incorporation.pdf', 'rb') as file:
      b64_document = base64.b64encode(file.read()).decode('utf-8')

  data = {
      "agent_key": "your-kyb-agent-key",
      "kyb_schema": {
          "id": "case-001",
          "self_attested_data": {
              "registered_business_name": "Example Business Inc.",
              "website": "https://example.com",
              "incorporation_date": "2023-01-01",
              "address_of_incorporation": {
                  "street_1": "123 Main St",
                  "city": "Wilmington",
                  "state": "DE",
                  "country_code": "US",
                  "postal_code": "19801"
              },
              "incorporation_documents": [
                  {
                      "file_name": "certificate_of_incorporation.pdf",
                      "b64_document": b64_document
                  }
              ]
          }
      },
      "webhook_url": "https://your-webhook.com/callback"
  }

  response = requests.post(url, json=data, headers={
      'Authorization': f'Bearer {api_key}',
      'Content-Type': 'application/json'
  })

  job_id = response.json()['job_id']
  print(f"Agent job started: {job_id}")
  ```
</CodeGroup>

<Info>
  **Agent configuration matters:** Your agent key determines which checks run and the pass/fail criteria. Work with Parcha to configure agents that match your compliance policies.
</Info>

***

## Approach 2: Prevalidation (Quick Check)

<Card title="Best for: Real-time document upload validation" icon="bolt">
  Use Prevalidation for **fast document type checking** without running full verification. Perfect for inline validation during user uploads.
</Card>

**When to use:**

* Real-time document upload flows where users expect quick feedback
* Pre-screening documents before committing to full verification
* Validating document type before user submits their application

**How it works:**

```mermaid theme={null}
sequenceDiagram
    participant User
    participant App
    participant API
    participant Check
    
    User->>App: Upload document
    App->>API: POST /runCheck (flags disabled + webhook_url)
    API-->>App: job_id
    API->>Check: Quick type validation only
    Note over Check: Skip visual verification<br/>Skip fraud check<br/>Skip field verification
    Check-->>API: Is valid document type?
    API-->>App: Webhook: {is_valid_document: true/false}
    App-->>User: Show quick feedback
```

Set all verification flags to `false` to skip visual verification and fraud checks. The system will only validate that the document is a valid type. Use a webhook to receive the result quickly.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.parcha.ai/api/v1/runCheck' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "agent_key": "your-agent-key",
    "check_id": "kyb.incorporation_document_verification",
    "kyb_schema": {
      "id": "case-001",
      "self_attested_data": {
        "registered_business_name": "Example Business Inc.",
        "incorporation_date": "2023-01-01",
        "incorporation_documents": [
          {
            "file_name": "certificate_of_incorporation.pdf",
            "b64_document": "BASE64_ENCODED_DOCUMENT"
          }
        ]
      }
    },
    "check_args": {
      "enable_visual_verification": false,
      "enable_fraud_check": false,
      "verify_incorporation_date": false,
      "verify_registration_number": false,
      "verify_address": false,
      "verify_no_high_risk_fraud": false
    },
    "webhook_url": "https://your-webhook.com/prevalidation-callback"
  }'
  ```

  ```python Python theme={null}
  import requests
  import base64

  api_key = 'YOUR_API_KEY'
  url = 'https://api.parcha.ai/api/v1/runCheck'

  with open('certificate_of_incorporation.pdf', 'rb') as file:
      b64_document = base64.b64encode(file.read()).decode('utf-8')

  # Prevalidation mode - all verification flags disabled
  data = {
      "agent_key": "your-agent-key",
      "check_id": "kyb.incorporation_document_verification",
      "kyb_schema": {
          "id": "case-001",
          "self_attested_data": {
              "registered_business_name": "Example Business Inc.",
              "incorporation_date": "2023-01-01",
              "incorporation_documents": [
                  {
                      "file_name": "certificate_of_incorporation.pdf",
                      "b64_document": b64_document
                  }
              ]
          }
      },
      "check_args": {
          "enable_visual_verification": False,
          "enable_fraud_check": False,
          "verify_incorporation_date": False,
          "verify_registration_number": False,
          "verify_address": False,
          "verify_no_high_risk_fraud": False
      },
      "webhook_url": "https://your-webhook.com/prevalidation-callback"
  }

  response = requests.post(url, json=data, headers={
      'Authorization': f'Bearer {api_key}',
      'Content-Type': 'application/json'
  })
  job_id = response.json()['id']
  print(f"Prevalidation job started: {job_id}")
  ```
</CodeGroup>

<Tip>
  **Webhook or Poll?** Provide a `webhook_url` (recommended) to receive results automatically as soon as prevalidation completes, or poll the `/getJobById` endpoint if webhooks aren't feasible for your setup.
</Tip>

***

## Approach 3: Full Verification

<Card title="Best for: Thorough document analysis" icon="shield-check">
  Use Full Verification for **complete document analysis** including visual verification, fraud detection, and data extraction.
</Card>

**When to use:**

* Final verification before approving a business application
* When you need visual element analysis (seals, signatures)
* When fraud detection is required
* When you need to verify specific data fields (dates, addresses)

**How it works:**

```mermaid theme={null}
sequenceDiagram
    participant App
    participant API
    participant Check
    participant Vision
    participant Fraud
    
    App->>API: POST /runCheck (flags enabled)
    API-->>App: job_id
    API->>Check: Start verification
    
    Check->>Check: OCR & text extraction
    Check->>Vision: Visual verification
    Note over Vision: Analyze seals, signatures<br/>Check visual authenticity
    Vision-->>Check: Visual result
    
    Check->>Fraud: Fraud analysis
    Note over Fraud: Metadata analysis<br/>Tampering detection
    Fraud-->>Check: Fraud result
    
    Check->>Check: Verify fields (date, address, etc.)
    Check-->>API: Complete result
    API-->>App: Webhook or poll result
```

Enable the verification options you need. Each adds additional analysis capabilities:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.parcha.ai/api/v1/runCheck' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "agent_key": "your-agent-key",
    "check_id": "kyb.incorporation_document_verification",
    "kyb_schema": {
      "id": "case-001",
      "self_attested_data": {
        "registered_business_name": "Example Business Inc.",
        "incorporation_date": "2023-01-01",
        "address_of_incorporation": {
          "street_1": "123 Main St",
          "city": "Wilmington",
          "state": "DE",
          "country_code": "US",
          "postal_code": "19801"
        },
        "incorporation_documents": [
          {
            "file_name": "certificate_of_incorporation.pdf",
            "b64_document": "BASE64_ENCODED_DOCUMENT"
          }
        ]
      }
    },
    "check_args": {
      "enable_visual_verification": true,
      "enable_fraud_check": true,
      "verify_incorporation_date": true,
      "verify_registration_number": true,
      "verify_address": true,
      "verify_no_high_risk_fraud": true
    },
    "webhook_url": "https://your-webhook.com/callback"
  }'
  ```

  ```python Python theme={null}
  import requests
  import base64

  api_key = 'YOUR_API_KEY'
  url = 'https://api.parcha.ai/api/v1/runCheck'

  with open('certificate_of_incorporation.pdf', 'rb') as file:
      b64_document = base64.b64encode(file.read()).decode('utf-8')

  # Full verification mode - all checks enabled
  data = {
      "agent_key": "your-agent-key",
      "check_id": "kyb.incorporation_document_verification",
      "kyb_schema": {
          "id": "case-001",
          "self_attested_data": {
              "registered_business_name": "Example Business Inc.",
              "incorporation_date": "2023-01-01",
              "address_of_incorporation": {
                  "street_1": "123 Main St",
                  "city": "Wilmington",
                  "state": "DE",
                  "country_code": "US",
                  "postal_code": "19801"
              },
              "incorporation_documents": [
                  {
                      "file_name": "certificate_of_incorporation.pdf",
                      "b64_document": b64_document
                  }
              ]
          }
      },
      "check_args": {
          "enable_visual_verification": True,
          "enable_fraud_check": True,
          "verify_incorporation_date": True,
          "verify_registration_number": True,
          "verify_address": True,
          "verify_no_high_risk_fraud": True
      },
      "webhook_url": "https://your-webhook.com/callback"
  }

  response = requests.post(url, json=data, headers={
      'Authorization': f'Bearer {api_key}',
      'Content-Type': 'application/json'
  })

  job_id = response.json()['id']
  print(f"Full verification job started: {job_id}")
  ```
</CodeGroup>

***

## Verification Options Reference

<Note>
  **Latency vs. Thoroughness Trade-off:** Each verification option adds scrutiny but increases processing time. Choose based on your risk tolerance and UX requirements.
</Note>

| Option                       | Default | Description                                                     | Latency Impact |
| ---------------------------- | ------- | --------------------------------------------------------------- | -------------- |
| `enable_visual_verification` | `true`  | Uses vision model to analyze seals, signatures, visual elements | +15-30 seconds |
| `enable_fraud_check`         | `false` | Sophisticated fraud and tampering analysis on document metadata | +1-2 minutes   |
| `verify_incorporation_date`  | `true`  | Fail if incorporation date has significant discrepancies        | Minimal        |
| `verify_registration_number` | `false` | Fail if registration number has significant discrepancies       | Minimal        |
| `verify_address`             | `false` | Fail if address has significant discrepancies                   | Minimal        |
| `verify_no_high_risk_fraud`  | `false` | Fail if HIGH\_RISK fraud indicators detected                    | Minimal        |

<Info>
  By default (without explicit options), the system performs OCR-based analysis using LLMs to extract and analyze text from the document.
</Info>

***

## Polling for Results

After starting a job, poll for completion:

<CodeGroup>
  ```bash cURL theme={null}
  curl 'https://api.parcha.ai/api/v1/getJobById?job_id=YOUR_JOB_ID&include_check_results=true' \
  -H 'Authorization: Bearer YOUR_API_KEY'
  ```

  ```python Python theme={null}
  import requests
  import time

  api_key = 'YOUR_API_KEY'
  job_id = 'YOUR_JOB_ID'

  while True:
      response = requests.get(
          'https://api.parcha.ai/api/v1/getJobById',
          headers={'Authorization': f'Bearer {api_key}'},
          params={'job_id': job_id, 'include_check_results': 'true'}
      )
      job_data = response.json()
      
      if job_data['status'].lower() == 'complete':
          check_result = job_data['check_results'][0]
          
          if check_result['passed']:
              print("✓ Document verified successfully")
              payload = check_result['payload']
              print(f"  Company: {payload.get('company_name')}")
              print(f"  Incorporation Date: {payload.get('incorporation_date')}")
          else:
              print(f"✗ Verification failed: {check_result.get('answer')}")
          break
      elif job_data['status'].lower() == 'failed':
          print("Job failed!")
          break
      
      time.sleep(5)
  ```
</CodeGroup>

***

## Using Webhooks

Instead of polling, configure a webhook to receive results automatically:

```javascript theme={null}
const express = require('express');
const app = express();
app.use(express.json());

app.post('/callback', (req, res) => {
  const jobData = req.body;
  
  if (jobData.status === 'complete') {
    const incorpCheck = jobData.check_results.find(
      check => check.command_id === 'kyb.incorporation_document_verification'
    );
    
    if (incorpCheck) {
      if (incorpCheck.passed) {
        console.log('✓ Document verified');
        console.log('Company:', incorpCheck.payload.company_name);
      } else {
        console.log('✗ Verification failed:', incorpCheck.answer);
      }
    }
  }
  
  res.sendStatus(200);
});

app.listen(3000);
```

For more details, see the [Webhooks documentation](/api-reference/webhooks).

***

## Decision Guide

<AccordionGroup>
  <Accordion title="I need to run a full compliance review with multiple checks">
    **Use: Approach 1 - Full Agent Job (`startKYBAgentJob`)**

    Include documents in your `kyb_schema` and let the agent handle document verification alongside web presence, sanctions screening, and other checks. You get unified pass/fail logic across all verification types.
  </Accordion>

  <Accordion title="I need quick validation during document upload">
    **Use: Approach 2 - Prevalidation (`runCheck` with flags disabled)**

    Set all verification flags to `false` for fast document type validation. Show users immediate feedback on whether their document is the right type before they submit.
  </Accordion>

  <Accordion title="I need thorough verification with fraud detection">
    **Use: Approach 3 - Full Verification (`runCheck` with flags enabled)**

    Enable `enable_visual_verification` and `enable_fraud_check` for comprehensive analysis. Use this for final verification before approving applications.
  </Accordion>

  <Accordion title="I want quick prevalidation followed by full verification">
    **Combine Approaches 2 and 3**

    1. Run prevalidation during upload for quick feedback
    2. Run full verification when user submits their complete application

    This gives users fast feedback while ensuring thorough verification before approval.
  </Accordion>
</AccordionGroup>

***

## Document Upload Options

When submitting documents for verification, you have two options for how to include the document data:

### Option A: Inline Base64 (Single Request)

Include the base64-encoded document directly in your verification request. This is the simplest approach for most use cases.

**How it works:**

```mermaid theme={null}
sequenceDiagram
    participant App
    participant API
    
    App->>App: Read file & base64 encode
    App->>API: POST /runCheck with b64_document
    Note over API: Document uploaded & verification started<br/>in single request
    API-->>App: job_id
```

**When to use:**

* Most common approach
* Documents under 10MB
* When you want simplicity

```python theme={null}
import base64

with open('document.pdf', 'rb') as file:
    b64_document = base64.b64encode(file.read()).decode('utf-8')

data = {
    "agent_key": "your-agent-key",
    "check_id": "kyb.incorporation_document_verification",
    "kyb_schema": {
        "id": "case-001",
        "self_attested_data": {
            "registered_business_name": "Example Business Inc.",
            "incorporation_documents": [
                {
                    "file_name": "certificate.pdf",
                    "b64_document": b64_document  # Document included inline
                }
            ]
        }
    },
    "check_args": {...}
}
```

***

### Option B: Pre-upload (Two-Step)

Upload the document first to get a URL, then reference that URL in your verification request. Useful when you need to manage documents separately.

**How it works:**

```mermaid theme={null}
sequenceDiagram
    participant App
    participant API
    participant Storage
    
    App->>API: POST /uploadB64Document
    API->>Storage: Store document
    Storage-->>API: document_url
    API-->>App: document_url
    
    App->>API: POST /runCheck with file_url
    API-->>App: job_id
```

**When to use:**

* Large documents
* When you need to reuse the same document across multiple checks
* When you want to manage document storage separately

**Step 1: Upload the document**

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.parcha.ai/api/v1/uploadB64Document' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "b64_document": "BASE64_ENCODED_CONTENT",
    "document_name": "certificate_of_incorporation.pdf"
  }'
  ```

  ```python Python theme={null}
  import requests
  import base64

  api_key = 'YOUR_API_KEY'

  with open('certificate_of_incorporation.pdf', 'rb') as file:
      b64_document = base64.b64encode(file.read()).decode('utf-8')

  response = requests.post(
      'https://api.parcha.ai/api/v1/uploadB64Document',
      headers={
          'Authorization': f'Bearer {api_key}',
          'Content-Type': 'application/json'
      },
      json={
          'b64_document': b64_document,
          'document_name': 'certificate_of_incorporation.pdf'
      }
  )

  document_url = response.json()['url']
  print(f"Document uploaded: {document_url}")
  ```
</CodeGroup>

**Step 2: Run verification with the URL**

```python theme={null}
data = {
    "agent_key": "your-agent-key",
    "check_id": "kyb.incorporation_document_verification",
    "kyb_schema": {
        "id": "case-001",
        "self_attested_data": {
            "registered_business_name": "Example Business Inc.",
            "incorporation_documents": [
                {
                    "file_name": "certificate_of_incorporation.pdf",
                    "url": document_url  # Reference the uploaded document
                }
            ]
        }
    },
    "check_args": {...}
}

response = requests.post(
    'https://api.parcha.ai/api/v1/runCheck',
    headers={'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'},
    json=data
)
```

***

### Comparison

| Option               | Requests | Best For                                                 |
| -------------------- | -------- | -------------------------------------------------------- |
| **A: Inline Base64** | 1        | Simple integration, most use cases                       |
| **B: Pre-upload**    | 2        | Large files, document reuse, separate storage management |

***

## Other Document Types

The same three approaches work for all document verification checks:

| Document Type             | Check ID                                       | Document Field                 |
| ------------------------- | ---------------------------------------------- | ------------------------------ |
| Incorporation Certificate | `kyb.incorporation_document_verification`      | `incorporation_documents`      |
| Proof of Address          | `kyb.proof_of_address_verification`            | `proof_of_address_documents`   |
| EIN Document              | `kyb.ein_document_verification`                | `ein_documents`                |
| Business Ownership        | `kyb.business_ownership_document_verification` | `business_ownership_documents` |
| Source of Funds           | `kyb.source_of_funds_document_verification`    | `source_of_funds_documents`    |

Simply swap the `check_id` and document field in your requests.
