PDF Text Extractor - Citable Pages & RAG Chunks avatar

PDF Text Extractor - Citable Pages & RAG Chunks

Pricing

$0.50 / 1,000 delivered nonempty pdf text pages

Go to Apify Store
PDF Text Extractor - Citable Pages & RAG Chunks

PDF Text Extractor - Citable Pages & RAG Chunks

Extract PDF text as original-numbered pages with document hashes, exact-offset RAG chunks and explicit coverage. Public HTTPS or base64 input. No OCR. Pay only for delivered nonempty pages.

Pricing

$0.50 / 1,000 delivered nonempty pdf text pages

Rating

0.0

(0)

Developer

Nathaniel Standridge

Nathaniel Standridge

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Categories

Security

Limited permissions

Share

PDF Text Extractor — Citable Pages & RAG Chunks

Turn text-based PDFs into original-numbered pages, exact-offset chunks and explicit coverage for document search, quotation tracking and RAG ingestion. Pay for a whole nonempty page only when it reaches the default dataset. Chunks and the coverage report are included.

This is a deterministic text-layer extractor, not OCR, an AI summarizer, an embedding service, a table parser or a layout-preserving converter. It does not determine whether a document is true, current or legally valid.

Start with a working PDF

{
"urls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
"maxPagesPerDocument": 10,
"maxResults": 10,
"chunkChars": 1200,
"chunkOverlap": 100,
"maxRunSeconds": 120
}

The checked public example has one page containing Dummy PDF file. A successful extraction costs $0.0005 in Actor result charges. Its source may change or become unavailable; errors are reported rather than replaced with sample text. Set the run's maximum charge in Console or through the API as an additional guard.

For your own PDFs, provide public direct HTTPS URLs or caller-supplied bytes:

{
"pdfs": [{"name": "supplier-handbook.pdf", "base64": "YOUR_PDF_BYTES_AS_BASE64"}],
"startPage": 1,
"maxPagesPerDocument": 50,
"maxResults": 100
}

Replace the base64 placeholder with an actual PDF. No data-URL prefix, password, cookie, token or login is accepted. Inline PDFs are useful when a document is not publicly hosted; you are responsible for permission to process it. The local helper below encodes a file without exposing it to another service:

import base64
import json
from pathlib import Path
payload = Path("supplier-handbook.pdf").read_bytes()
actor_input = {"pdfs": [{"name": "supplier-handbook.pdf", "base64": base64.b64encode(payload).decode()}]}
Path("actor-input.json").write_text(json.dumps(actor_input))

One complete workflow

  1. Supply the PDFs from your recurring document intake batch. Run with bounded page and spending limits.
  2. Read OUTPUT first. Check every document's selected range, skipped pages, unprocessed pages and documentComplete before treating a batch as complete.
  3. Export default-dataset JSON. Upsert pages using (documentSha256, pageNumber) and retain textSha256 to detect identical extracted content.
  4. Send each chunks[].text to your own indexing or embedding system, retaining its citation, page number and source metadata. Chunk creation is included; no embedding provider is called.
  5. When quoting a result, use the original physical page and source PDF. Validate important quotations visually, especially in tables or multi-column documents.

For a simple integration, save your exported dataset as pages.json and run this adapter:

import json
from pathlib import Path
pages = json.loads(Path("pages.json").read_text())
with Path("chunks.jsonl").open("w") as output:
for page in pages:
for chunk in page["chunks"]:
record = {"id": chunk["chunkId"], "text": chunk["text"],
"citation": chunk["citation"], "pageNumber": page["pageNumber"],
"documentSha256": page["documentSha256"], "sourceUrl": page["sourceUrl"]}
output.write(json.dumps(record, ensure_ascii=False) + "\n")

There is no shared cross-run document cache or hidden subscription. A new run may extract and charge the same pages again; deduplication applies within a run and its recovery only.

Output contract

Each paid dataset row is a complete, nonempty normalized text page:

FieldMeaning
documentSha256, documentBytesSHA-256 and byte length of the exact downloaded or supplied PDF, before parsing
sourceId, sourceType, sourceUrl, finalUrl, sourceNameInput association, public URL provenance or inline display name; inapplicable values are null
pageNumber, pageCountOne-based physical page and total physical pages; printed labels may differ
text, textSha256, characterCountFull accepted normalized page text, its UTF-8 SHA-256 and Unicode code-point count
chunksDeterministic within-page slices with chunkId, startChar, endChar, text and citation
citationSource-byte identity and physical page, such as sha256:...#page=1; an identifier, not a hosted download link
observationIdDeterministic ID incorporating document bytes, page, normalized text and chunk settings
qualityWarningsshort_text_layer below 40 characters or replacement_character_present; heuristics, not a correctness score
extractor, extractedAtPinned extraction implementation and run-plan timestamp

The following excerpt shows the exact text and offsets from the checked W3C example. The fields listed in the table are included in actual dataset rows; IDs and provenance are omitted here for readability:

{
"pageNumber": 1,
"pageCount": 1,
"text": "Dummy PDF file",
"characterCount": 14,
"chunks": [{"startChar": 0, "endChar": 14, "text": "Dummy PDF file"}]
}

Offsets are zero-based Unicode code points, with an exclusive end: in Python, page["text"][startChar:endChar] exactly equals the chunk text. They are not byte, token or JavaScript UTF-16 offsets. In JavaScript, slice Array.from(page.text) to use these offsets. Chunk boundaries are fixed character windows, not sentence or token boundaries. Overlap repeats text intentionally. Chunks never cross pages.

Normalization converts CRLF/CR to LF; removes C0 controls except tab/newline, DEL and surrogate code points; and strips surrounding whitespace. It does not apply Unicode normalization, merge paragraphs or reconstruct layout. Internal spaces and line breaks are otherwise retained from the extractor. Pages exceeding a safety limit are skipped entirely, never silently truncated.

Free coverage report

OUTPUT includes the requested scope and one entry per retained input source. Each entry reports available source metadata, total pages, selectedStart, selectedEnd, unselectedPageCount, per-page status, empty/skipped/delivered counts, unprocessed selected pages, errors and any stop reason. Redirect provenance is recorded when a download succeeds.

  • complete: true means all configured page windows were processed and every accepted nonempty page was delivered. It does not mean every PDF page or every visual element was extracted.
  • documentComplete: true additionally requires that the selected window covers that entire physical PDF.
  • A startPage beyond a document's last page is a successful empty window. The report retains the actual page count, zero selected pages and documentComplete: false; this does not imply the PDF itself has no text.
  • Blank/image-only pages produce no paid rows. The status empty means no extractable text was found, not proof that the visual page is blank. A scanned PDF may complete with zero rows.
  • Duplicate normalized URLs are removed before download. Identical document bytes are parsed once; later aliases point to duplicateOf. Repeated text on different physical pages is retained because those are distinct citations.
  • Result, charge, time, parser and plan-size limits leave explicit partial coverage. A budget-limited run delivers complete pages only. Not-started sources can have unknown page counts (null).

If all attempted documents fail, the run fails after saving free diagnostics. A mix of useful pages and failures is partial. Malformed or unsupported inputs do not become paid error rows.

Inputs and hard bounds

Input or boundDefaultMaximum / rule
urls plus pdfs—1–10 submitted documents total; URLs first, then inline PDFs
Downloaded PDF—10 MiB each; HTTPS on port 443; public DNS only
Inline PDFs—4 MiB decoded total, strict base64
startPage11–10,000
maxPagesPerDocument501–100 physical pages in each window
maxResults1001–300 delivered nonempty pages total
chunkChars1,200200–4,000 Unicode code points
chunkOverlap1000–500, and strictly less than half chunkChars
maxRunSeconds12030–240; use a platform timeout of at least 300 seconds
Page text—100,000 normalized code points; entire oversized page skipped
Parser—256 MiB address space, 12 CPU seconds and 20 wall seconds per document
Decoded stream—2 MiB, plus bounded filter, parsing-work and page-tree limits
Frozen result plan—20 MiB including reserved metadata space

Runtime limits deliberately favor safe, predictable batches over processing every unusual PDF. Strict parsing can reject PDFs that a desktop viewer repairs. A parser timeout or process failure can discard that document's still-undelivered text; it is not charged.

Pricing

$0.50 per 1,000 delivered nonempty text pages ($0.0005 per page), no startup event. All chunks from a page are included. Blank, duplicate, failed, skipped, filtered-out-by-page-window and undelivered pages are uncharged. One page is one dataset item, not one chunk.

Examples: 20 delivered pages cost $0.01; 100 cost $0.05. These are Actor result charges, not a promise about your plan's other platform charges. Set a spending limit and inspect coverage after capped runs. A run can deliver less than requested when PDFs lack text layers or encounter safety limits.

Billing uses only Apify's native default-dataset event. The result plan is frozen before writes. An uncertain append is not retried; recovery checks the existing dataset and pending checkpoint. If that outcome cannot be verified, the run stops rather than risk duplicate pages or charges. A verified same-run restart reuses the frozen plan without refetching changed PDFs.

Safety, privacy and limitations

Public downloads validate every DNS answer, pin a public address with TLS hostname verification, validate each redirect destination and check robots rules at each PDF origin. Download retries are bounded safe reads; exhausted rate limits pause collection. No credentials, cookies or proxy are forwarded. Private/local destinations and nonstandard ports are rejected.

Parsing runs in a separate resource-limited process without inherited runtime credentials. PDF JavaScript, launch actions, attachments and external resources are not executed or fetched. This is not a general-purpose security sandbox. Encrypted and copy-restricted PDFs are rejected before attempting even an empty password; there is no unlocking feature.

The owned billable dataset client disables automatic redirects and retries and restricts requests to the Apify API origin. This statement does not apply to upstream SDK lifecycle/key-value storage clients, whose pinned defaults remain unchanged. Public-PDF fetching is a separate manually validated path.

Your inline base64 is part of Apify's stored input; text and provenance are saved in the dataset and run records. Treat storage access and retention accordingly. Temporary downloaded PDFs are removed locally after processing; this does not delete the platform input or output. Avoid sensitive URL queries. No customer files are sent to an AI provider.

Image-only scans require a separate OCR workflow. Reading order, ligatures, columns, headers, tables and custom font encodings may be imperfect. A text layer can omit or misrepresent visible content. A short warning list is not a guarantee of high quality. No original-document download is hosted by this Actor.

Free alternatives such as pypdf and Poppler are good choices when you manage the execution environment. This Actor adds bounded Apify batching, citable page/chunk output, explicit partial coverage and delivered-page charging; it does not claim exclusive extraction technology.

Local development validation

This section is for factory maintainers with the local source checkout, not a requirement for Store users. The examples/ and tools/ paths are repository developer artifacts, not downloadable Store attachments. The customer integration above is self-contained.

uv sync --frozen
uv run python -B -m unittest discover -s tests -v
npm ci --ignore-scripts --no-audit --no-fund
npm run test:schema
node tools/validate_apify_artifacts.mjs .

tools/prepare_cases.py creates private synthetic qualification inputs. tools/local_smoke.py retains source PDFs only under ignored private evidence and compares extraction with the independent Poppler pdftotext executable. Synthetic pages also have a known-text oracle. tools/validate_cloud.py checks actual dataset rows against schemas, frozen pages, hashes, chunks, coverage and independent source evidence. Unit tests exercise malformed/encrypted/expanding PDFs, deadlines, SSRF/robots/redirects, caps, duplicates and actual pinned-client ambiguous-write behavior.

Dependency: pypdf under its BSD-3-Clause license. Its text extraction guidance explains why text layers, complex layouts and decompressed streams need care.