# Synthetic Invoice OCR Ground Truth Dataset Generator (`jserle/ocr-ground-truth-document-factory`) Actor

Synthetic invoice OCR ground truth dataset generator: reproducible test invoices as PNG, raster PDF and JSON with word boxes, lines, reading order, table cells and typed fields, for OCR, IDP and document AI evaluation. Seeded and deterministic; 1 to 16 pages per run. $0.05/page.

- **URL**: https://apify.com/jserle/ocr-ground-truth-document-factory.md
- **Developed by:** [Jonathan Serle](https://apify.com/jserle) (community)
- **Categories:** AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 generated pages

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Synthetic Invoice OCR Ground Truth Dataset Generator

Generates synthetic invoice test documents with exact OCR ground truth: for every page you get a PNG image, a raster-only PDF with the same pixels, and an annotation JSON with every word's bounding box, line structure, reading order, table cells and typed invoice fields. Labels come from the renderer itself, not from a human annotator or a second OCR engine, so they are exact.

Use it to score an OCR, IDP or document-understanding pipeline against known-correct labels, to build a reproducible invoice dataset of any size for CI and regression tests, or to benchmark extraction accuracy across clean and scan-degraded renders of the same geometry. Every page is visibly marked `SYNTHETIC TEST DOCUMENT - NOT PAYABLE`, and all names, addresses, identifiers and amounts are invented.

### Who uses this

- **OCR and IDP engineering teams** measuring word error rate, box IoU, reading order, and field extraction accuracy against exact labels, without collecting, redacting or licensing real invoices.
- **Document AI and LLM evaluation teams** that need a labeled invoice dataset for key-information extraction (KIE) benchmarks, described by its input seed instead of stored and shipped around.
- **Pipeline and QA teams** feeding a document pipeline a fixed, reproducible corpus in CI instead of a folder of real customer files.
- **Robustness testing** comparing clean renders against deterministic scan degradation at the same geometry.

### What you get

Each run produces one to sixteen single-page, invoice-shaped test documents. For every page:

- **Page image (PNG)** at 144, 200, or 300 DPI
- **Raster-only PDF** containing exactly the same final pixels as the PNG
- **Annotation JSON** with the full ground truth:
  - `tokens`: every word with text, pixel and point bounding boxes, font, and reading order
  - `lines`: line text, boxes, role, and the tokens each line contains
  - `entities`: typed field labels (invoice number, dates, vendor and customer blocks, subtotal, tax, total) linked back to their tokens and lines
  - `tables`: table regions with rows, columns, column names, and per-cell boxes
  - `invoice`: the structured record behind the page, including line items and reconciled decimal amounts
  - `page`: dimensions in pixels and points, DPI, bbox format, and coordinate origin
  - `full_text`, `seed`, `schema_version`, and SHA-256 hashes of the delivered image and PDF

**Dataset**: one item per successfully generated and validated page:

```json
{
  "schemaVersion": "invoice-ground-truth/v0",
  "generatorVersion": "0.1.0",
  "documentId": "SYN-0000002D-0003",
  "seed": 45,
  "templateId": "invoice_grid_v0",
  "profile": "scan_light",
  "dpi": 200,
  "widthPx": 1700,
  "heightPx": 2200,
  "tokenCount": 112,
  "entityCount": 41,
  "recordKeys": {
    "image": "SYN_0000002D_0003_PAGE_PNG",
    "pdf": "SYN_0000002D_0003_PAGE_PDF",
    "annotations": "SYN_0000002D_0003_ANNOTATIONS_JSON"
  },
  "sha256": { "image": "...", "pdf": "...", "annotations": "..." },
  "synthetic": true,
  "payable": false
}
```

**Key-value store**: three records per page, named by the `recordKeys` above: the PNG, the raster PDF, and the annotation JSON. A run-level `OUTPUT` record reports `requestedPages` and `completedPages`.

Fetch the dataset for the index, then pull each artifact by its key. Every dataset item is only pushed after its three artifacts are stored, so a `recordKeys` entry always resolves.

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `seed` | integer | required | First deterministic document seed, 0 to 2,147,483,647. Page *n* of a run uses `seed + n`. |
| `count` | integer | 1 | Number of pages to generate, 1 to 16. |
| `template_id` | string | `auto` | `auto`, or one of four structural layouts: `invoice_classic_v0`, `invoice_compact_v0`, `invoice_grid_v0`, `invoice_sidebar_v0`. |
| `profile` | string | `clean` | `clean`, `scan_light`, or `scan_hard`. Scan profiles apply deterministic degradation and keep the ground-truth geometry aligned to the rendered pixels. |
| `dpi` | integer | 144 | Exactly 144, 200, or 300. Any other value is rejected. |
| `schema_version` | string | `invoice-ground-truth/v0` | Pinned output contract. |

Unknown input fields are rejected rather than silently ignored, so a typo fails fast instead of producing an unexpected corpus.

#### Example inputs

**A clean 16-page evaluation batch, mixed layouts** (OCR accuracy baseline):

```json
{
  "seed": 1000,
  "count": 16,
  "template_id": "auto",
  "profile": "clean",
  "dpi": 300
}
```

**The same pages degraded as a hard scan** (robustness comparison at identical geometry: same seeds, different profile):

```json
{
  "seed": 1000,
  "count": 16,
  "template_id": "auto",
  "profile": "scan_hard",
  "dpi": 300
}
```

**A fixed single-layout CI fixture** (regression test that must never change):

```json
{
  "seed": 42,
  "count": 4,
  "template_id": "invoice_grid_v0",
  "profile": "scan_light",
  "dpi": 200
}
```

### Determinism

A given `seed`, `template_id`, `profile`, `dpi`, and Actor version always produce byte-identical output. The SHA-256 hashes in each dataset item let you verify this. Re-running the same input reproduces the same corpus, and adjacent seeds give you independent documents, so an evaluation set can be described by its input alone instead of being stored and shipped around. Generate larger corpora by launching several runs with non-overlapping seed ranges.

### Pricing

**$0.05 per generated page.** You pay only for pages that are generated, validated, and delivered. A page that fails validation is not charged.

### Data source and licence

There is no external data source. Every page is rendered by this Actor from its own templates and a seeded generator; the Actor accepts no caller-supplied document text, logos, addresses, images, URLs, or templates, and reads nothing from the web. Output is yours to use for testing and evaluation under the restrictions below.

### Limitations

- 1 to 16 pages per run. Generate larger corpora by launching several runs with non-overlapping seed ranges.
- Single-page English USD invoice-shaped fixtures only.
- Four structural layouts; clean, light-scan, and hard-scan profiles; 144, 200, or 300 DPI.
- Labels are renderer-owned, not human-reviewed. They describe exactly what was drawn.
- This Actor does not reproduce the statistical distribution of real invoices and makes no privacy, compliance, or production-readiness claim.

### Not for

These pages are test fixtures, not documents. They contain no real people, companies, or transactions, they are visibly watermarked as synthetic and not payable, and they must not be used for billing, payment requests, identity construction, deception, or fraud.

### FAQ

#### Where can I get an invoice OCR dataset with ground truth?

Here, on demand. Each page ships with word-level boxes, lines, reading order, table cells and typed fields produced by the renderer, so the labels are exact rather than annotated. Set `seed` and `count` and the dataset is defined by those two numbers.

#### What does the OCR ground truth JSON contain?

`tokens` (every word with text, pixel and point boxes, font and reading order), `lines`, `entities` (invoice number, dates, vendor and customer blocks, subtotal, tax, total), `tables` with per-cell boxes, the structured `invoice` record with line items, `page` geometry, `full_text`, the seed, the schema version and SHA-256 hashes of the image and PDF.

#### Can I use this to benchmark invoice data extraction or key-information extraction?

Yes. `entities` links each typed field to its tokens and lines, and `invoice` carries the reconciled decimal amounts and line items, so you can score field-level extraction as well as raw OCR.

#### How do I test OCR on scanned documents versus clean ones?

Run the same `seed`, `count`, `template_id` and `dpi` twice with `profile` set to `clean` and then `scan_light` or `scan_hard`. The degradation is deterministic and the ground-truth geometry stays aligned to the rendered pixels.

#### Is the output reproducible?

Yes. The same input on the same Actor version produces byte-identical PNG, PDF and JSON; the SHA-256 hashes on every dataset item let you verify it.

#### Are these real invoices or real companies?

No. Every name, address, identifier and amount is invented, and every page is visibly marked as a synthetic test document that is not payable.

### Related datasets

Other datasets by the same author, all pay-per-record with source, refresh cadence and licence stated on each listing:

**Nursing home and long-term care data**

- [Nursing Home Deficiency Citations (CMS) Scraper](https://apify.com/jserle/nursing-home-deficiency-citations)
- [Nursing Home Fines & Civil Money Penalties (CMS) Scraper](https://apify.com/jserle/nursing-home-penalties-fines)
- [Nursing Home Directory & Ownership (CMS Form 671) Scraper](https://apify.com/jserle/ltc-facility-characteristics)
- [Nursing Home Daily Nurse Staffing (CMS PBJ) Scraper](https://apify.com/jserle/cms-nursing-home-staffing-daily)
- [Nursing Home Change of Ownership (CMS SNF CHOW) Scraper](https://apify.com/jserle/snf-change-of-ownership-tracker)
- [New York Nursing Home Bed Availability (NYSDOH) Scraper](https://apify.com/jserle/ny-nursing-home-bed-census)

**Medicare provider enrollment data**

- [Medicare Revoked Providers List (CMS Revocations) Scraper](https://apify.com/jserle/medicare-revoked-providers)
- [Medicare Opt-Out Providers List (CMS Affidavits) Scraper](https://apify.com/jserle/medicare-opt-out-physicians)
- [Medicare Revalidation Due Date List (CMS) Scraper](https://apify.com/jserle/medicare-revalidation-due-leads)

**Public procurement and RFP feeds**

- [E-Rate Form 470 RFP Scraper (USAC School & Library Bids)](https://apify.com/jserle/erate-form-470-rfp-feed)
- [NYC PASSPort Procurement Scraper (RFPs, Bids & Contracts)](https://apify.com/jserle/nyc-passport-procurement)

# Actor input Schema

## `schema_version` (type: `string`):

Pinned output contract. Leave as is unless a newer version is published.

## `seed` (type: `integer`):

Seed for the first page, 0 to 2,147,483,647. Page n of the run uses seed + n. The same seed always regenerates the same page, so an evaluation set can be described by its seed range alone.

## `count` (type: `integer`):

How many single-page documents to generate in this run, 1 to 16. Each page is billed once.

## `template_id` (type: `string`):

Structural layout of the generated page. Auto varies the layout deterministically across seeds.

## `profile` (type: `string`):

Clean render, or a deterministic scan degradation. Light and hard scan apply capture degradation while keeping the ground-truth geometry aligned to the rendered pixels.

## `dpi` (type: `integer`):

Page resolution. Supported values are exactly 144, 200, and 300. Any other value is rejected.

## Actor input object example

```json
{
  "schema_version": "invoice-ground-truth/v0",
  "seed": 0,
  "count": 1,
  "template_id": "auto",
  "profile": "clean",
  "dpi": 144
}
```

# Actor output Schema

## `results` (type: `string`):

One dataset item per successfully generated and validated page.

## `files` (type: `string`):

PNG, raster PDF, and annotation JSON records.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("jserle/ocr-ground-truth-document-factory").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("jserle/ocr-ground-truth-document-factory").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call jserle/ocr-ground-truth-document-factory --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jserle/ocr-ground-truth-document-factory"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/PKfz1xCEToaE3xH9x/builds/vLcdPyHhMmm7Mplsx/openapi.json
