# Historical Records Extractor (`hal8/historical-records-extractor`) Actor

Turn scans of handwritten and historical records into clean text. One dataset row per page, each with a signed delivery receipt. Pay per delivered page in USD.

- **URL**: https://apify.com/hal8/historical-records-extractor.md
- **Developed by:** [HAL 8](https://apify.com/hal8) (community)
- **Categories:** AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 page processeds

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

## Historical Records Extractor

Reads scans of old, handwritten and archival paper and gives you back clean text, one row per page.

This is built for the documents that ordinary text extraction gives up on. Census sheets with forty columns and a hundred cramped rows. Ship manifests written in a clerk's hurried cursive. Parish and church registers. Military service and casualty records. Land, probate and institutional ledgers. Faded, skewed, over-ruled, bled-through paper.

Every page comes back with a signed delivery receipt. The receipt binds the text we returned to the exact page image we were sent, so you can show later that a transcript is the one you were actually delivered, and check it without asking us anything.

You pay per delivered page, in USD. Pages that fail are not charged.

### Who this is for

- Genealogists and family historians working through parish, census and immigration records
- Archivists and local history societies digitising a collection
- Records researchers who need a searchable transcript of a bound volume
- Legal, probate and land researchers reading old registers
- Academic historians building a dataset out of handwritten sources
- Anyone who has a folder of scans and needs the text out of them

If your documents are modern printed pages, a generic text extractor will be cheaper and will do fine. Come here for the difficult paper.

### What you give it

| Field | Required | Default | What it does |
| --- | --- | --- | --- |
| **Document URLs** | Yes | none | Direct links to the scans you want read. PDF, PNG, JPEG and WebP. One link per line. |
| **Output format** | No | Markdown | `Markdown` keeps reading order and turns tables into text tables. `HTML` keeps richer structure. `JSON (layout blocks)` returns the page as positioned blocks. |
| **Max pages per document** | No | 50 | Safety cap for big PDFs. Between 1 and 200. Pages past the cap are skipped and reported, never billed. |
| **Include delivery receipts** | No | On | Attaches the signed delivery record to every row. |

Notes on input:

- The Actor reads files from a link. There is no upload box inside it. If your scans are on your computer, put them in a key-value store on this platform or on any file host first, then paste the direct links.
- PDFs are split into pages and each page is read on its own.
- A document that cannot be fetched, is larger than 100 MB, or is not one of the four accepted types is skipped. It is listed in the run summary with the reason. It is never silently dropped.

### What you get back

One dataset row per page. A successful row carries:

| Field | What it holds |
| --- | --- |
| `source` | The document link this page came from |
| `page` | Page number inside that document, starting at 1 |
| `status` | `ok` |
| `output_format` | `markdown`, `html` or `json`, whichever you asked for |
| `content` | The extracted text for this page |
| `billed` | `true` if this page was charged, `false` if it was delivered without a charge |
| `receipt` | The signed delivery record for this page, present when receipts are switched on |

A page that could not be read produces a row with `status: failed`, the `source` and `page`, and an `error` explaining what went wrong. Failed pages are not charged.

About `billed`: it is almost always `true`. It comes back `false` in one case, and we would rather tell you than hide it. If you set a maximum cost for the run and the run reaches it while a page is already being read, that page is finished and handed to you anyway, unbilled, because the work was already done. You keep the page and you are not charged for it.

The run also writes a `SUMMARY` record to the run's key-value store:

`documents`, `pages_prepared`, `pages_settled`, `pages_settled_unbilled`, `pages_failed`, `pages_unprocessed_spend_limit`, `documents_skipped` (with a reason for each), `charged_pages`, `charging_disabled`, `output_format`.

That summary is the honest count of what happened in the run. Use it to reconcile a big job against your invoice.

### Signed delivery receipts

Each delivered page carries a small signed record. It contains a fingerprint of the page image we were sent, a fingerprint of the text we returned, how long the work took, an identifier for the service that produced it, a delivery reference, and a signature over all of it.

What that buys you:

- **Proof of what was delivered.** If a transcript is questioned months later, the receipt shows that this exact text was returned for that exact page image. Change one character of either and the fingerprints stop matching.
- **A check that does not depend on us.** The receipt travels inside your dataset. Verifying it is arithmetic on data you already hold, not a lookup against a service that has to still be running.
- **A clean chain for a client or an archive.** If you deliver transcripts to somebody else, you can hand over the receipts with them.

The receipt records the delivery. It is not a statement about accuracy, and it does not certify that the reading is correct. Nothing can do that except a human reading the page.

The receipt itself ships in every run today. If you want the step-by-step verification procedure and the signing key, ask through the Issues tab and we will send them.

### Pricing

Pay per event, in USD.

- **Per delivered page.** You are charged once for each page that is read and returned with its receipt.
- **Failed pages cost nothing.** Skipped documents and pages past your page cap cost nothing.
- **You can cap a run.** Set a maximum total cost before you start it. When the run reaches the cap it stops taking new pages and shuts down cleanly, and you keep everything already delivered.

Start with two or three pages of your own material before you commit a whole collection. That is the only honest way to find out how this performs on your handwriting, your scanner and your century.

### Limitations, stated plainly

We would rather you read this now than find it out during a run.

- **Roughly one page per minute.** Pages are processed one at a time today. A 50-page book is close to an hour of run time. Plan around it and use the page cap. We are working on it and this line gets updated when it changes, not before.
- **Large documents are processed page by page.** There is no whole-document mode. A table that runs across two pages comes back as two rows, and stitching it back together is your step.
- **Each page is read on its own.** Context does not carry from one page to the next, so a name spelled out on the cover page does not help disambiguate a scrawl on page nine.
- **Scan quality decides the outcome.** Faint ink, bleed-through from the reverse, tight bindings, skew and low-resolution phone photos all reduce what can be read. Higher-resolution scans read better.
- **We publish no accuracy figure.** We will not quote one for your material either, because we have not seen your material. Run a sample.
- **No upload box.** Files have to be reachable by a direct link.
- **100 MB per document.** Anything bigger is skipped with a reason.

### FAQ

**Can it read old cursive handwriting?**
That is what it is built for. How well it does on any particular hand depends on the hand and the scan, so run a couple of pages before committing a collection.

**What document types can I send?**
PDF, PNG, JPEG and WebP. PDFs get split into pages automatically.

**How do I send files that are on my computer?**
Upload them to a key-value store on this platform, or to any file host, and paste the direct links. The Actor reads from links.

**How long will my job take?**
Roughly a minute a page at the moment. Twenty pages is about twenty minutes. Very large jobs are better split into several runs.

**What happens if a page fails?**
You get a row for it with `status: failed` and the reason. You are not charged for it. The rest of the run continues.

**How do I stop a job from costing more than I want?**
Set a maximum total cost for the run. The run stops taking new pages once it reaches that, and shuts down cleanly rather than erroring.

**Why is one of my rows marked `billed: false`?**
Because it was delivered after your run hit its cost cap, while it was already being read. You get the page for free. See the `billed` note above.

**Do you keep my documents?**
Your files are fetched for the run, read page by page, and returned to your dataset. The delivery record carries fingerprints of the page and of the text, not the text itself.

**Can you guarantee the transcript is correct?**
No, and neither can anyone else. Machine reading of handwritten historical paper produces a first pass that a human then corrects. What we guarantee is that the text you received is provably the text we delivered for that page.

**What is the receipt actually for?**
Three things: proving to a third party what you were delivered, catching any later tampering with a transcript, and giving you a check you can run without us.

**Is there a bulk or volume price?**
Ask through the Issues tab.

### Data handling

Plainly, so there are no surprises:

- **Your document content leaves the platform.** Each page image is sent to an external extraction service, which performs the extraction and returns the text. That request carries the page image, its file type and the output format you chose. Nothing else from your input goes with it.
- **A per-user identifier accompanies each request.** Every request carries a one-way hash of the platform user id, plus the name of the venue the run came from, for operational accounting on the supply side. It is not reversible into your account name, email or any other profile field, but it is a stable pseudonymous identifier rather than anonymity: the same user produces the same value on every run, so requests from one user can be correlated with each other.
- **Storage stays in your account.** Extracted content and receipts are written to your own run's dataset and key-value store; this Actor keeps no copy of its own. What the external extraction service retains is governed by its own terms, not by this Actor.

### About this service

This Actor is operated end to end by its publisher. Pricing is in USD.

Questions, problems and bug reports go through the Issues tab on this listing. That is the fastest route to us and it is the one we watch.

# Actor input Schema

## `documentUrls` (type: `array`):

Direct links to the scans to extract: PDF, PNG, JPEG or WebP. For local files, upload them to a key-value store (or any file host) first and paste the direct link here. Leave as-is to try the built-in example (a 1940 US census page).

## `outputFormat` (type: `string`):

Shape of the extracted content per page. Markdown returns plain text with tables written out as text; HTML returns richer markup; JSON returns layout blocks.

## `maxPagesPerDocument` (type: `integer`):

Safety cap for large PDFs. Pages beyond this count are skipped and reported.

## `includeReceipts` (type: `boolean`):

Attach the signed per-page delivery receipt to every dataset row. The receipt records which engine handled the page and lets you verify the delivery offline, without an account and without trusting us.

## Actor input object example

```json
{
  "documentUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg/1920px-1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg"
  ],
  "outputFormat": "markdown",
  "maxPagesPerDocument": 50,
  "includeReceipts": true
}
```

# Actor output Schema

## `pages` (type: `string`):

One dataset row per page: the extracted content and its signed delivery receipt.

# 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 = {
    "documentUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg/1920px-1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hal8/historical-records-extractor").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 = { "documentUrls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg/1920px-1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("hal8/historical-records-extractor").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 '{
  "documentUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg/1920px-1940_Census_Population_Schedules_-_Alabama_-_Jefferson_County_-_ED_68-27_-_DPLA_-_bf9de06eabc448362f928c6bf7afe561_%28page_1%29.jpg"
  ]
}' |
apify call hal8/historical-records-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hal8/historical-records-extractor"
        }
    }
}

```

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/aKIoezBzsb7RXlZoE/builds/7YSWrZvP8b8KSJwn6/openapi.json
