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

# Validate Document

> Submit a document for asynchronous validation

Submit a base64-encoded document for asynchronous compliance validation.

<Note>
  **Custom policy rules run automatically.** Any rules you've activated via
  [Activate Rules](/api-reference/custom-policies/activate-rules) are applied on every validation
  for your API key — you do **not** select them via `document_category` or `document_metadata`.
  Those fields only choose which of ZeroDrift's built-in (default) rule scenarios also run. To get
  your custom rules plus broad default coverage, use `document_category: "scenario_all_general"`.
</Note>

## Request Body

<ParamField body="document_bytes" type="string" required>
  Base64-encoded document content (PDF, DOCX, etc.)
</ParamField>

<ParamField body="document_category" type="string">
  Pre-defined category for the document. Required if `document_metadata` is not provided.

  Options: `retail_investor_letter`, `retail_fact_sheet_registered_fund`, `retail_fact_sheet_non_registered`, `pitch_book_registered_fund`, `pitch_book_non_registered`, `scenario_retail_investor_letter`, `scenario_retail_fact_sheet_registered_fund`, `scenario_retail_fact_sheet_non_registered`, `scenario_pitch_book_registered_fund`, `scenario_pitch_book_non_registered`, `scenario_all_general`, `scenario_email_general`, `scenario_mnpi_focused`

  Use `scenario_all_general` for full coverage across all default rules (including MNPI detection).
</ParamField>

<ParamField body="document_metadata" type="object">
  Detailed metadata for precise rule matching. Required if `document_category` is not provided.

  <Expandable title="metadata properties">
    <ParamField body="audience_hint" type="string" required>
      Target audience. Options: `retail_US`, `retail_nonUS`, `institutional`, `accredited_investor`, `qualified_client_SEC`, `qualified_purchaser`, `professional_client_UK`, `eligible_counterparty`, `plan_participant_ERISA`, `plan_sponsor`, `intermediary_distributor`, `prospect`, `existing_client`, `former_client`, `HNW`, `UHNW`, `media_press`, `regulator_examiner`, `internal_only`
    </ParamField>

    <ParamField body="jurisdiction" type="string" required>
      Regulatory jurisdiction: `US`
    </ParamField>

    <ParamField body="product_class" type="string" required>
      Product classification: `registered_fund` or `non_registered`
    </ParamField>

    <ParamField body="document_type" type="string" required>
      Document type: `investor_letter`, `fact_sheet`, `pitch_book`, `presentation`, `email`, `website_copy`, `rfp_response`, `research_report`, `prospectus_extract`, `social_post`, `advisor_newsletter`
    </ParamField>

    <ParamField body="product_types" type="array" required>
      Array of product types (e.g., `["Fund__mutual_fund"]`). Common values include `Fund__mutual_fund`, `Fund__etf`, `Fund__hedge_fund`, `Fund__private_equity_fund`, `Advisory_Account__sma`, and many more.
    </ParamField>
  </Expandable>
</ParamField>

<Note>
  At least one of `document_category` or `document_metadata` must be provided.
</Note>

## Scanned PDF Support (OCR)

The validation service automatically handles scanned PDFs using AWS Textract OCR. No additional parameters are needed — OCR is triggered transparently when text extraction yields insufficient content.

**How it works:**

1. The service first attempts standard text extraction via `pypdf`
2. If a page yields fewer than 50 characters, it is classified as a scanned/image page
3. Scanned pages are automatically sent to AWS Textract for OCR
4. The OCR text is merged with any text-extracted pages before validation

**Three PDF cases:**

| Case                                 | Behavior                                            |
| ------------------------------------ | --------------------------------------------------- |
| **Text-only PDF**                    | Standard text extraction, no OCR                    |
| **Fully scanned PDF**                | All pages sent to Textract OCR                      |
| **Mixed PDF** (text + scanned pages) | Only scanned pages are OCR'd, text pages kept as-is |

**Limits:**

| Workflow                                            | OCR Limit                                          |
| --------------------------------------------------- | -------------------------------------------------- |
| `POST /api/validate_stream/` (direct base64 upload) | Up to 50 pages, 10MB per page (sync, page-by-page) |
| Presigned URL + `POST /api/validate_stream_start/`  | Up to 3,000 pages, 500MB total (async via S3)      |

<Note>
  Scanned PDFs may take longer to process due to OCR. For direct uploads via this endpoint, OCR is performed page-by-page (sync). For large scanned documents (50+ pages), use the [presigned URL workflow](/api-reference/validation/presigned-url) which enables asynchronous Textract processing with higher limits.
</Note>

## Response

<ResponseField name="job_id" type="string">
  Unique identifier for the validation job
</ResponseField>

<ResponseField name="status" type="string">
  Job status: `accepted`
</ResponseField>

<ResponseField name="message" type="string">
  Status message
</ResponseField>

<ResponseField name="timestamp" type="string">
  ISO 8601 timestamp
</ResponseField>

<ResponseExample>
  ```json 202 Accepted theme={null}
  {
    "job_id": "abc123def456",
    "status": "accepted",
    "message": "Validation request queued for processing",
    "timestamp": "2026-01-08T10:30:00Z"
  }
  ```
</ResponseExample>

## Example

<CodeGroup>
  ```bash cURL theme={null}
  # Encode document to base64
  DOC_BASE64=$(base64 -i document.pdf)

  curl -X POST "https://{api-url}/api/validate_stream/" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"document_bytes\": \"$DOC_BASE64\",
      \"document_category\": \"retail_investor_letter\"
    }"
  ```

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

  API_KEY = "YOUR_API_KEY"
  API_BASE = "https://{api-url}"

  with open("document.pdf", "rb") as f:
      doc_bytes = base64.b64encode(f.read()).decode()

  response = requests.post(
      f"{API_BASE}/api/validate_stream/",
      headers={
          "x-api-key": API_KEY,
          "Content-Type": "application/json"
      },
      json={
          "document_bytes": doc_bytes,
          "document_category": "retail_investor_letter"
      }
  )

  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const fs = require('fs');
  const axios = require('axios');

  const API_KEY = 'YOUR_API_KEY';
  const API_BASE = 'https://{api-url}';

  const docBytes = fs.readFileSync('document.pdf').toString('base64');

  const response = await axios.post(
    `${API_BASE}/api/validate_stream/`,
    {
      document_bytes: docBytes,
      document_category: 'retail_investor_letter'
    },
    {
      headers: {
        'x-api-key': API_KEY,
        'Content-Type': 'application/json'
      }
    }
  );

  console.log(response.data);
  ```
</CodeGroup>
