> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withverifa.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withverifa.com/_mcp/server.

# Tutorial: Custom Document Types & AI Extraction

> Create custom document types with extraction hints, upload documents, and extract structured fields using Verifa's private infrastructure.

This tutorial walks you through creating a custom document type, uploading a
document, running automated classification and extraction, and viewing the
results. By the end you'll have a working pipeline that extracts structured
fields from any document your business needs to verify.

## What you'll learn

* Creating a custom document type with extraction hints
* Uploading a standalone document via the API
* How classification matches documents to types
* Running verification and field extraction
* Reading extraction results and confidence scores
* Using document groups for recency checks

## Prerequisites

* A Verifa account ([sign up](https://app.withverifa.com))
* A **sandbox** API key (starts with `vk_sandbox_`). Find it under
  **Developers > API > API Keys** in the dashboard.

## Introduction

Verifa ships with built-in document types for common identity documents
(passports, driver's licenses, national IDs). But many businesses need to
verify documents that fall outside the standard set: health insurance cards,
business registrations, utility bills with specific fields, professional
licenses, and more.

Custom document types let you define exactly which fields to extract from a
document. When a document is classified against your custom type, Verifa
reads the document and extracts every field you specified -- all processing
runs on private infrastructure, with no data sent to third-party services.

## Step 1: Create a custom document type

Create a document type for a health insurance card. The `extraction_hints`
array tells Verifa which fields to look for.

```bash
curl -X POST https://api.withverifa.com/api/v1/settings/document-types \
  -H "X-API-Key: vk_sandbox_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Health Insurance Card",
    "slug": "health_insurance_card",
    "extraction_hints": [
      "insurer_name",
      "member_id",
      "group_number",
      "plan_type",
      "effective_date",
      "member_name"
    ]
  }'
```

```json
{
  "id": "dtype_abc123",
  "name": "Health Insurance Card",
  "slug": "health_insurance_card",
  "extraction_hints": [
    "insurer_name",
    "member_id",
    "group_number",
    "plan_type",
    "effective_date",
    "member_name"
  ],
  "is_system": false,
  "created_at": "2026-04-10T14:00:00Z"
}
```

You can also create custom document types in the dashboard under
**Settings > Document Types**. The form provides the same options as the API.

## Step 2: Upload a document

Upload a health insurance card image as a standalone document (not attached to
a verification session).

```bash
curl -X POST https://api.withverifa.com/api/v1/documents \
  -H "X-API-Key: vk_sandbox_your_key_here" \
  -F "file=@insurance-card-front.jpg" \
  -F "document_type=other" \
  -F "original_filename=insurance-card-front.jpg"
```

```json
{
  "id": "doc_def456",
  "document_type": "other",
  "classification": null,
  "status": "pending",
  "mime_type": "image/jpeg",
  "file_size_bytes": 245120,
  "original_filename": "insurance-card-front.jpg",
  "download_url": "https://api.withverifa.com/api/v1/documents/doc_def456/download?expires=1744300800&sig=a1b2c3...",
  "preview_url": "https://api.withverifa.com/api/v1/documents/doc_def456/preview?expires=1744300800&sig=a1b2c3...",
  "extracted_fields": null,
  "extracted_fields_confidence": null,
  "created_at": "2026-04-10T14:05:00Z"
}
```

The document is stored and ready for classification and extraction. The
`preview_url` provides a server-side JPEG rendering (useful for PDFs with
page navigation).

## Step 3: Classification

When you trigger verification (Step 4), classification runs automatically.
The system compares the document against all system types and your custom
types, then selects the best match.

You can also trigger classification manually:

```bash
curl -X POST https://api.withverifa.com/api/v1/documents/doc_def456/classify \
  -H "X-API-Key: vk_sandbox_your_key_here"
```

```json
{
  "id": "doc_def456",
  "classification": "health_insurance_card",
  "status": "pending",
  "updated_at": "2026-04-10T14:06:00Z"
}
```

Verifa analyzed the document and matched it to your custom
"Health Insurance Card" type. Classification runs entirely on private
infrastructure -- document data is never sent to third-party services.

## Step 4: Run verification

Trigger the verification pipeline, which includes field extraction when the
matched type has `extraction_hints`.

```bash
curl -X POST https://api.withverifa.com/api/v1/documents/doc_def456/verify \
  -H "X-API-Key: vk_sandbox_your_key_here"
```

```json
{
  "id": "doc_def456",
  "status": "completed",
  "classification": "health_insurance_card",
  "updated_at": "2026-04-10T14:07:00Z"
}
```

The pipeline runs OCR, classification (if not already done), and structured
field extraction in sequence.

## Step 5: View results

Retrieve the document to see extracted fields and confidence scores.

```bash
curl https://api.withverifa.com/api/v1/documents/doc_def456 \
  -H "X-API-Key: vk_sandbox_your_key_here"
```

```json
{
  "id": "doc_def456",
  "document_type": "other",
  "classification": "health_insurance_card",
  "status": "completed",
  "mime_type": "image/jpeg",
  "file_size_bytes": 245120,
  "original_filename": "insurance-card-front.jpg",
  "download_url": "https://api.withverifa.com/api/v1/documents/doc_def456/download?expires=1744300800&sig=a1b2c3...",
  "preview_url": "https://api.withverifa.com/api/v1/documents/doc_def456/preview?expires=1744300800&sig=a1b2c3...",
  "extracted_fields": {
    "insurer_name": "Nationwide Health Insurance",
    "member_id": "NHI-882-456-7721",
    "group_number": "GRP-40291",
    "plan_type": "PPO",
    "effective_date": "2025-01-01",
    "member_name": "Jane A. Doe"
  },
  "extracted_fields_confidence": {
    "insurer_name": 0.997,
    "member_id": 0.981,
    "group_number": 0.964,
    "plan_type": 0.991,
    "effective_date": 0.973,
    "member_name": 0.998
  },
  "created_at": "2026-04-10T14:05:00Z",
  "updated_at": "2026-04-10T14:07:00Z"
}
```

### Understanding confidence scores

Each field in `extracted_fields_confidence` has a score between 0.0 and 1.0.
The score is computed by checking whether the extracted value's tokens actually
appear in the underlying OCR text.

| Range      | Interpretation                                                                                    |
| ---------- | ------------------------------------------------------------------------------------------------- |
| 0.9 -- 1.0 | High confidence. The extracted value closely matches OCR text.                                    |
| 0.5 -- 0.9 | Moderate confidence. Some tokens matched; review recommended.                                     |
| Below 0.5  | Likely hallucination. The LLM produced a value not found in the document. Flag for manual review. |

## Step 6: Document groups and recency checks

Document groups let you enforce recency requirements. For example, you might
require that a supporting document was issued within the last 90 days.

> **Note**
>
> This is the recency mechanism for your own custom document types. The built-in
> `proof_of_address_check` has its own policy — accepted subtypes, per-type and
> per-country recency windows, and match thresholds — configured under
> **Settings → Proof of address**. See
> [Proof of address](/core-concepts/verifications-checks#proof-of-address).

```bash
curl -X POST https://api.withverifa.com/api/v1/settings/document-groups \
  -H "X-API-Key: vk_sandbox_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Recent Insurance",
    "document_type_slugs": ["health_insurance_card"],
    "recency_config": {
      "max_age_days": 365,
      "date_field": "effective_date"
    }
  }'
```

When a document in this group is verified, the system checks whether the
`effective_date` field falls within the allowed window. If the document is
older than the configured threshold, the recency check fails.

## Dashboard

Everything in this tutorial is also available in the Verifa dashboard.
Navigate to **Documents** in the left sidebar to see:

* **Document list** -- all standalone documents with status, classification,
  and upload date. Use the filters to narrow by type, status, or date range.
* **Document detail** -- full metadata, extraction results displayed as a
  structured card, classification details, check results, and a PDF/image
  preview with page navigation.
* **Upload** -- drag-and-drop document upload directly from the dashboard.

The extraction fields card shows each extracted value alongside its confidence
score, color-coded by the ranges described above.

## Next steps

* Browse the [API Reference](/api-reference) for the full Documents API
* Learn about [Verifications & Checks](/core-concepts/verifications-checks)
  to understand how document checks fit into the broader pipeline
* Set up [Webhooks](/integration-guides/webhooks-guide) to get notified when document
  verification completes