# PDF Text and Metadata Extractor (`jj_toolworks/pdf-text-metadata-extractor`) Actor

Convert public text PDFs into page-numbered text and metadata with download links, document hashes, and explicit coverage. Up to 25 total pages and 10 MB per PDF.

- **URL**: https://apify.com/jj_toolworks/pdf-text-metadata-extractor.md
- **Developed by:** [JJ Toolworks](https://apify.com/jj_toolworks) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 complete pdf extractions

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

## PDF Text and Metadata Extractor

Convert public text PDFs into page-numbered text, document metadata and downloadable JSON/text files. Every input gets a status explaining what was extracted and whether every page yielded embedded text.

Use it for public reports, supplier documentation, product sheets, warranties, training materials and document batches that feed a search or retrieval workflow. The output keeps source URLs and page numbers so downstream answers can point back to their source.

### Run an example

```json
{
  "pdf_urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}
```

Supply 1–50 directly downloadable public PDF URLs. Each document must fit within 10 MB, 25 **total** pages and a 30-second parsing budget. A 26-page PDF is over the included limit even if only its first page contains useful text.

### Industry examples you can reuse

These public reference documents demonstrate three workflows with the same input schema. Local downloads and the existing parser returned text from every page on October 10, 2026. The subsequent Apify cloud batch on build 0.1.4 completed the NIST and DOE documents, while the OSHA source failed to download and produced no success event. Source access can differ between execution environments; a successful batch run does not mean every document succeeded.

#### Construction and field-service training — source-dependent example

Convert an OSHA ladder-safety handout into numbered pages for a searchable training-resource library. Keep the original PDF link next to the text so staff can open the source layout.

```json
{
  "pdf_urls": [
    "https://www.osha.gov/sites/default/files/publications/PORTABLE_LADDER_QC.pdf"
  ]
}
```

The sample returned two text pages in the local parser check. In Apify cloud run `safyZzipBjp0cDkGK`, this input instead returned `failed` with `FetchError: http_error` and no success event; its numeric HTTP status was not recorded. This source is **not verified as accessible from Apify**. Reuse the input pattern for public equipment instructions, manufacturer warranties and contractor training handouts that fit the limits, and inspect each document result.

#### Small-business IT and managed-service providers

Prepare NIST's nine-page Small Business Quick-Start Guide for a reference index. All nine pages returned text in the build 0.1.4 cloud batch. The page number and source URL can travel with every search result; the Actor extracts the document's text without evaluating a business's security controls.

```json
{
  "pdf_urls": [
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.1300.pdf"
  ]
}
```

#### Facilities and building-product research

Ingest a short technical sheet into a facilities reference collection. This one-page heat-pump research sheet is an archived January 2012 publication, useful as a document-ingestion example. Its page returned text in the build 0.1.4 cloud batch.

```json
{
  "pdf_urls": [
    "https://www1.eere.energy.gov/buildings/publications/pdfs/building_america/tech_highlight_heat_pump.pdf"
  ]
}
```

Combine these URLs in one `pdf_urls` array for a three-document batch. The verified cloud batch produced **two complete documents and one unbilled source-access failure**, with two `document-completed` events. The industry batch therefore also demonstrates per-input failure reporting. Supplier sheets, education resources and research briefs can use the same workflow; the source documents and downstream labels change.

### What you receive

- A `document` row for every input URL, including duplicates and failures.
- A `page` row for every assessed page of an in-scope parsed document, containing its page number, embedded text and status.
- Download links for page-numbered `.txt` and `.json` artifacts when pages were assessed.
- A final `exports` row linking to `DOCUMENTS.json`, the input manifest.

Document fields include `input_url`, `source_url`, `document_sha256`, `file_bytes`, `page_count`, `pages_with_text`, `coverage`, `metadata`, `status`, `json_url`, `text_url` and `fetched_at`. Metadata fields such as title and author are included only when present in the PDF. The SHA-256 identifies the downloaded bytes; it is useful for deduplication and traceability.

### Turn the output into a reusable document index

Download the default dataset as JSON and save it as `dataset.json`. This optional `jq` command creates one JSON line per text page from complete documents:

```sh
jq -c '.[] | select(.row_type == "page" and .document_status == "complete" and .status == "text") | {id: (.document_sha256 + ":" + (.page_number | tostring)), text, source_url, page_number, document_sha256, fetched_at}' dataset.json > search-pages.jsonl
```

| Downstream field | Actor value | Purpose |
| --- | --- | --- |
| Record ID | `document_sha256` plus `page_number` | Upsert the same bytes and page without adding duplicate search records |
| Content | Page `text` | Send to your own keyword index or retrieval pipeline |
| Citation | `source_url` plus `page_number` | Keep an answer or search result traceable to the original document |
| Version | `document_sha256` | Detect changed downloaded bytes; a hash change alone does not prove the text changed |
| Intake result | Document `status`, `coverage` and `reason` | Route incomplete or unsupported documents for separate review |

For repeat runs, maintain a source-URL-to-current-hash mapping in your downstream system. Replace the active version only after a successful complete document result; retain prior usable records when a refresh fails. This Actor produces the text and provenance records; search hosting, embeddings and cross-run version management belong to the downstream workflow.

### Understand completion before automating

`complete` means **every document page returned nonempty embedded text within the limits**. It does not mean every visual element was recognized or the original reading order was perfectly reconstructed. Text inside images is not extracted. A page with both embedded text and an image can therefore have additional visible text that is not included.

A document with any page that has no embedded text is conservatively marked incomplete or `no_embedded_text`. This includes intentionally blank pages and scans. Other outcomes include `encrypted`, `page_limit`, `file_size_limit`, `malformed`, `resource_limit`, `timeout` and `failed`. Page statuses distinguish empty text from parse or size failures when the parser can do so.

PDF columns, complex fonts, diagrams and tables can produce an imperfect text order. Review representative documents before relying on an unattended downstream workflow.

### Pricing unit

One success event is one complete text PDF within the stated limits. The current rate appears in the Pricing tab. Encrypted, scanned/image-only, mixed or incomplete documents, over-limit files and failed downloads do not produce success events. Duplicate URLs and identical downloaded bytes are not charged twice within the same run.

The document price includes its page text and metadata. OCR is not included, and this Actor does not interpret invoices, calculate financial values, reconstruct tables or provide document-specific advice.

### Limits that keep batches predictable

Each PDF is parsed in an isolated process that can be terminated at its deadline. In addition to the file/page limits, decoded content is capped at 8 MB per page and 32 MB across the document, and extracted document text is limited to 1 MB. Highly compressed or unusually complex files can therefore be unsupported even when their downloaded file is small. A batch also remains subject to the platform's run output and request limits.

Public HTTP/HTTPS sources only. Private network URLs, credentials, encrypted-document passwords and browser login sessions are unsupported. Use a direct downloadable URL rather than a viewer page.

### Efficient variations

Use saved input presets for supplier manuals, public research reports, construction product sheets or training resources. The same page output can support separate downstream keyword indexes and document schemas without adding another extraction engine. If your workload is predominantly scanned files, choose an OCR product with page-based pricing.

For an extraction issue, provide the run ID, returned status and a public reproducible source document. Avoid including confidential documents in a public issue.

### Price and run controls

The launch price is **$5 per 1,000 completed units** ($0.005 per event), as defined above. Check the current Pricing tab before running. Status and duplicate rows do not add this custom event. Set a maximum charge appropriate for your batch; a run stops when its event budget is exhausted. `RUN-SUMMARY` records actual accepted events and execution totals.

Automatic replay of an interrupted run is disabled to prevent duplicate charges. Keep the available output, then start a new run for a new execution. A later run is a new billable execution. Completed dataset rows and individually saved files remain available when a budget stops a run. Combined exports, manifests, and snapshots assembled at the end may be absent after a budget stop or interruption; use a sufficient run budget when you need those aggregate files. The initial release uses documented input limits and public source access; external source changes can require maintenance.

# Actor input Schema

## `pdf_urls` (type: `array`):

Public, directly downloadable text-PDF URLs. Maximum 25 total pages, 10 MB and 30 seconds parsing per document; no OCR or encrypted PDFs.

## Actor input object example

```json
{
  "pdf_urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

# 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 = {
    "pdf_urls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jj_toolworks/pdf-text-metadata-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 = { "pdf_urls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("jj_toolworks/pdf-text-metadata-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 '{
  "pdf_urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call jj_toolworks/pdf-text-metadata-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jj_toolworks/pdf-text-metadata-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/sIiM9ky2SdOmK07WO/builds/vRLpqskIgN6W9Q76C/openapi.json
