# EN 10204 3.1 MTC to VDA 231-301 (`leonyu/en10204-mtc-parser`) Actor

Convert user-supplied EN 10204 3.1 mill test certificate PDFs into VDA 231-301 JSON. No website crawling.

- **URL**: https://apify.com/leonyu/en10204-mtc-parser.md
- **Developed by:** [Leon Yu](https://apify.com/leonyu) (community)
- **Categories:** Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 document 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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does EN 10204 3.1 Mill Test Certificate Parser do?

This Actor parses an **EN 10204 3.1 mill test certificate** (an MTC, or inspection certificate) into **VDA 231-301 JSON**. You upload the PDF, or you paste a direct file URL. It reads the text layer and, when a page has almost no characters, Tesseract. It downloads only the files you supply. It does not crawl a website, follow links, or log in.

The JSON shape follows the [VDA 231-301 EN 10204 schema](https://github.com/VDA231-301/VDA_231-301__EN_10204). Each dataset item also carries a canonical extract, warnings, a confidence score, and `needsReview` / `schemaValid` flags. Schedule a run, call it through the [Apify API](https://docs.apify.com/api/v2), or export the dataset as JSON, CSV, or Excel.

Press **Start** and the run uses a synthetic one-page certificate with a text layer: `schemaValid` is true and `confidence` is 0.95. The same file is committed as `samples/sample-en10204-3.1.pdf`.

### Why use this EN 10204 parser?

Purchasing, incoming inspection, and material traceability all start from the mill certificate: heat number, dimensions, product standard, chemical composition, and mechanical properties. The Actor turns that PDF into a dataset you can schedule, monitor, call from the Apify API, and send on with a webhook.

- Header lines and tables are aligned by position, so a blank min/max cell is not filled from the next number.
- Chemistry, strength, and elongation are checked for units, numeric range, min/max versus measured, composition sums, Rm/Re, and an IIW carbon-equivalent estimate.
- A mismatch is a warning. The measured number is kept.
- Output is validated against the vendored VDA schema. Schema errors are returned with the document.

#### Use cases

- **Purchasing.** Certificate number, customer order, grade, and heat number, one dataset item per PDF.
- **Incoming inspection.** Chemistry and tensile rows with the printed min/max beside the measured value, plus a warning when the measured value sits outside those limits.
- **Traceability.** Heat number tied to size, the product standard, and the EN 10204 3.1 statement.
- **Systems of record.** Download JSON, CSV, or Excel, or read the same items from the Apify API.

### What data can you extract?

| Field | What it is |
| --- | --- |
| `canonical.document` | Certificate number, dates, customer, order, signatory, mill, EN 10204 standard |
| `canonical.product` | Grade, size, thickness, width, length, product standard |
| `canonical.heats` | Heat number, element min/max/measured, mechanical rows |
| `certificate` | VDA 231-301 `TestingProject` with chemistry and tensile test series |
| `warnings` | Limits, sums, unit conversions, and reference-data notes |
| `schemaValid` | Whether `certificate` validates against the VDA schema |
| `confidence` | 0 to 1 score from the fields that were actually read |
| `needsReview` | True when the schema fails, confidence is under 0.75, or a warning was raised |

CAS numbers are not printed on a typical certificate. When a row is mapped into the VDA composition array, the CAS entry comes from a public element registry and the item gets an info warning. Carbon equivalent (`Ceq`, `CE`, `CEV`) is not stored as cerium.

`confidence` counts fields that were filled. On a scan it can stay high while many of those fields still disagree with a ground-truth sheet. Use `schemaValid`, `needsReview`, and `warnings` together with it.

### How to extract an EN 10204 certificate

1. Open the Actor. The **Certificate PDF URLs** field is prefilled with the synthetic text-layer sample below. Replace it with your own direct PDF URL, or upload PDFs instead.
2. Set **Maximum pages per PDF** (default 10, hard cap 25). Leave **OCR scanned pages** on if the files are scans. The prefilled sample has a text layer, so OCR is not used.
3. Set the run memory to **1024 MB** (the default). The platform default run timeout is **900 seconds**. The Actor stops on its own at **820 seconds** so it can still write `SUMMARY` and a status message before that kill.
4. Run the Actor. Open the dataset for one item per file, and the key-value store key `SUMMARY` for counts and the prices that were in effect.

The same input JSON is the body of an Apify API run. The API tab on this Actor shows the endpoint.

#### Example input

```json
{
  "fileUrls": [
    "https://raw.githubusercontent.com/yushengliang0530-alt/mtc-public-samples/main/sample-en10204-3.1.pdf"
  ],
  "maxPages": 10,
  "enableOcr": true,
  "ocrLanguage": "eng"
}
```

That URL is a public copy of the synthetic certificate (customer "Example Steel Buyer", mill "Example Mill Works", grade S355J2, heat `H240311`). The same PDF is also committed in this Actor repository at `samples/sample-en10204-3.1.pdf`. It is the file a Store run uses when you keep the prefill.

Uploads use `certificateFiles` (key-value store file keys). Provide at least one of `fileUrls` or `certificateFiles`.

#### Example output

A run of that sample returns a record like this. The full local item, including the VDA `certificate` object, is in `samples/example-output.json` (`sourceUrl` is null there because that file was read from disk).

```json
{
  "sourceUrl": "https://raw.githubusercontent.com/yushengliang0530-alt/mtc-public-samples/main/sample-en10204-3.1.pdf",
  "fileName": "sample-en10204-3.1.pdf",
  "status": "succeeded",
  "pageCount": 1,
  "pagesProcessed": 1,
  "ocrUsed": false,
  "extractionMethod": "text",
  "confidence": 0.95,
  "needsReview": false,
  "schemaValid": true,
  "canonical": {
    "document": {
      "certificate_number": "MTC-2024-0017",
      "issuing_date": "2024-03-11",
      "standard": "EN 10204 3.1",
      "customer": "Example Steel Buyer",
      "order_number": "C123456"
    },
    "product": {
      "material": "S355J2",
      "thickness_mm": 12.0,
      "width_mm": 1500.0,
      "length_mm": 6000.0,
      "product_standard": "EN 10025-2"
    },
    "heats": [
      {
        "heat_number": "H240311",
        "elements": {
          "C": { "measured": 0.16, "min": 0.1, "max": 0.22, "unit": "%" }
        },
        "mechanical": [
          { "symbol": "Rp0.2", "unit": "MPa", "min": 355.0, "measured": 412.0 }
        ]
      }
    ]
  }
}
```

`certificate` on the same item is the VDA 231-301 document (`TestingProject`, optical emission series, tensile series). You can download the dataset in JSON, CSV, Excel, or HTML.

### How much does EN 10204 extraction cost?

Public pricing is **pay per event**. There is **no start fee**.

| Event | Price | When it is charged |
| --- | --- | --- |
| `document-processed` | $0.02 | Once per PDF that was read into a result |
| `page-processed` | $0.005 | Once per page that produced text or OCR words, on a document that succeeded |

The prefilled one-page sample is **$0.025** in events ($0.02 + $0.005). A four-page certificate that succeeds is **$0.04** ($0.02 + 4 × $0.005). Failed downloads, files that are not PDFs, and files with no readable text are stored as `status: "failed"` and are not charged. If the remaining `ACTOR_MAX_TOTAL_CHARGE_USD` budget cannot cover the next document and its pages, that file is skipped and not charged.

The Actor reads these prices at runtime with `ChargingManager.get_pricing_info()`. The code does not contain a price table, and it does not charge a start event or `apify-default-dataset-item`.

Apify also bills the memory you allocate. Measured on this repository, with the process resident set size (Linux `ru_maxrss`):

| Document | Pages | OCR | Time | Peak RSS | Field accuracy |
| --- | --- | --- | --- | --- | --- |
| `samples/sample-en10204-3.1.pdf` | 1 | no | 0.047 s | 58 MB | 21/21 = **1.00** |
| Diler phone scan `diler-07-07-2025-rerun-41-44.pdf` (optional) | 4 | yes | 121.2 s | 183 MB | 108/387 = **0.279** |

At the default **1024 MB** and about **$0.30 per compute unit** (1 GB-hour), the text certificate is about `1 * (0.047/3600) * 0.30 ≈ $0.000004` of platform compute, and the 4-page scan is about `121.2/3600 * 0.30 ≈ $0.010`. Re-measure with `python scripts/benchmark.py` if you change the OCR path.

#### Phone scans and OCR

The default run is the text-layer sample above. It passes VDA schema validation with high confidence and does not use OCR.

Scanned certificates are optional. Leave **OCR scanned pages** on for a photograph or a scan. The optional check in this repository is the GoSmarter Diler phone scan `data/benchmark/diler-07-07-2025-rerun-41-44.pdf` (MIT). On that file the benchmark recovered the certificate number, issuing date, EN 10204 3.1, customer, order number, and the BS 4449 grade line, plus heat numbers and many composition cells (including C, Si, Mn, P, and S on the heats whose digits were readable). Cells the OCR did not read stayed empty.

`schemaValid` stayed false and `needsReview` stayed true. The form does not print the laboratory order, the signatory, or the VDA tensile symbols `Rp0.2`, `A50`, and `Ag`, and those values are not invented. Confidence on that scan was 0.85 because many fields were filled. Field accuracy against the published ground truth was 0.279. Treat a phone scan as a draft for review.

### Input

See the input tab for every field. `fileUrls` must be http(s) links to the PDF itself. The downloader resolves DNS once, refuses private, loopback, and link-local addresses (including cloud metadata), pins the TCP connection to the checked IP, and applies the same checks to every redirect. The whole download, including redirects and the body, has a **20 second** hard deadline that the input cannot raise. Only ports 80 and 443 are allowed.

OCR has the same kind of cap. Each page has a **120 second** wall-clock deadline, one Tesseract call has a **20 second** cap, and all OCR in one file has a **480 second** deadline. Input cannot raise these. A page that runs out of time keeps any heats already read and is marked in `warnings`; later pages are skipped once the file deadline is reached. The log records download start and finish, each page's OCR start and finish (including orientation, header, and chemistry-row lines), and every charge attempt.

The default run timeout is **900 seconds**. The Actor's own budget is **820 seconds**: under that platform kill, and above the 480 second document OCR cap, so one scanned file can finish and the run can still exit with a status message. With OCR left on, another file starts only when a full document OCR budget still fits. A scanned multi-page PDF should stay within that one-file budget. Prefer a text-layer PDF when the mill can provide one.

`maxPages` limits how many pages are parsed and how many `page-processed` events can be charged for that file.

### Output

The default dataset has one item per file. You can download that dataset as JSON, HTML, CSV, or Excel. The key-value store record `SUMMARY` has requested, succeeded, failed, and skipped counts, pages charged, and the event prices returned by `get_pricing_info`.

Info warnings (for example CAS numbers taken from the element registry) do not by themselves set `needsReview`. A measured value outside the printed min/max, a composition sum above 100%, or a reversed limit does.

Some decimals cannot satisfy the schema's `multipleOf: 0.0001` check once they are IEEE floats. Those rows stay at the number that was read, `schemaValid` is false, and `needsReview` is true.

### Tips

- Prefer a text-layer PDF when the mill can provide one. OCR is there for scans and is the slow part. Keep a scanned multi-page PDF inside the one-file OCR budget (120 seconds per page, 480 seconds per file) so it finishes inside the 900 second default run timeout.
- Keep memory at 1024 MB. The measured peak RSS on the 4-page Diler scan is in the cost table above; 1024 MB leaves room for Tesseract and the PDF renderer.
- Set a max charge on the run. The Actor checks the budget before it charges `document-processed` and `page-processed`.

### FAQ, disclaimers, and support

#### Do I need a mill login or a website URL?

Paste a direct PDF link, or upload the file. The Actor reads that file and stops. Certificates you upload can contain names of signatories and customer staff. That is personal data. Process only files you are allowed to use.

#### Will a phone scan validate?

Use the prefilled text-layer sample when you want a schema-valid result. A phone scan can return heats and chemistry with empty cells, `schemaValid: false`, and `needsReview: true`. See **Phone scans and OCR** above. The Diler scan is that optional check. It is not the prefilled run.

The VDA schema files under `schema/` are MIT licensed, copyright Verband der Automobilindustrie e.V. The Diler ground truth and PDF under `data/benchmark/` are from GoSmarter.ai, also MIT. See the `LICENSE` files next to those copies.

Questions and bugs belong on the Actor's Issues tab. Programmatic runs are described on the API tab.

### Local checks

```bash
pip install -r requirements.txt -r requirements-dev.txt
python -m pytest -q
python scripts/benchmark.py
```

The benchmark writes `samples/benchmark_report.json`.

# Actor input Schema

## `fileUrls` (type: `array`):

Direct http(s) URLs of EN 10204 PDF files you are allowed to process. The Actor downloads each file once. It does not crawl sites, follow page links, or log in.

## `certificateFiles` (type: `array`):

EN 10204 PDFs uploaded to the run key-value store. Use this when the file should not be fetched from a URL.

## `maxPages` (type: `integer`):

Pages after this limit are not read and are not charged. The hard maximum is 25.

## `enableOcr` (type: `boolean`):

Run Tesseract on a page when its text layer has almost no characters. Each page has a 120 second limit. One Tesseract call on that page has a 20 second limit.

## `ocrLanguage` (type: `string`):

Tesseract language code installed in the image. The default image includes English.

## Actor input object example

```json
{
  "fileUrls": [
    "https://raw.githubusercontent.com/yushengliang0530-alt/mtc-public-samples/main/sample-en10204-3.1.pdf"
  ],
  "maxPages": 10,
  "enableOcr": true,
  "ocrLanguage": "eng"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset of parsed certificates, validation warnings, and per-file failures.

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

Key-value store containing the SUMMARY record for the run.

# 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 = {
    "fileUrls": [
        "https://raw.githubusercontent.com/yushengliang0530-alt/mtc-public-samples/main/sample-en10204-3.1.pdf"
    ],
    "maxPages": 10,
    "ocrLanguage": "eng"
};

// Run the Actor and wait for it to finish
const run = await client.actor("leonyu/en10204-mtc-parser").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 = {
    "fileUrls": ["https://raw.githubusercontent.com/yushengliang0530-alt/mtc-public-samples/main/sample-en10204-3.1.pdf"],
    "maxPages": 10,
    "ocrLanguage": "eng",
}

# Run the Actor and wait for it to finish
run = client.actor("leonyu/en10204-mtc-parser").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 '{
  "fileUrls": [
    "https://raw.githubusercontent.com/yushengliang0530-alt/mtc-public-samples/main/sample-en10204-3.1.pdf"
  ],
  "maxPages": 10,
  "ocrLanguage": "eng"
}' |
apify call leonyu/en10204-mtc-parser --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,leonyu/en10204-mtc-parser"
        }
    }
}
```

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/vVfAiDSUmYZoWDiWg/builds/sgOFaxHzweD50PwTC/openapi.json
