PDF Inspector: Scanned PDF Detector & Metadata Audit
Pricing
Pay per usage
PDF Inspector: Scanned PDF Detector & Metadata Audit
Bulk PDF audit before OCR or text extraction: which pages are scanned and need OCR, which files are encrypted, what the metadata leaks (author, local paths), embedded JavaScript and attachments, PDF/A and PDF/UA conformance, unembedded fonts, and every link inside the document.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Ai-Q Labs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 hours ago
Last modified
Categories
Share
Point it at a list of PDF URLs. It tells you, per file, whether text can be pulled out of it at all, what the file leaks about whoever made it, and whether anything inside it should stop you opening it.
This is the step before a PDF text extractor, not another one. A plain extractor hands back an empty string for a scanned page and gives you no way to tell that apart from a genuinely blank one. On a batch of a thousand documents that difference decides how much OCR you pay for.
Free to run. No API key, no proxy, no browser. Two HTTP requests per file at most: the download, and nothing else unless you turn link checking on.
The question it answers that a text extractor cannot
For every page, the Actor separates four cases that all look identical in extractor output:
| Page kind | What it means | What to do |
|---|---|---|
text | 200+ characters of extractable text | Extract it, you are done |
sparse | Short but real text - a title page, a divider | Extract it |
image_with_stamp | Almost no text next to images: a page number or Bates stamp burned onto a scan | Send to OCR |
image_only | No text, images present | Send to OCR |
blank | No text and no images. Genuinely empty | Skip it - OCR would cost money and return nothing |
Those roll up into one field per document, textLayer:
full- every page can be readpartial- some pages can, some cannot. This is the dangerous one: a plain extractor returns content, so nothing looks wrong, and you silently lose the scanned pagesnone- the whole document is a scanempty- every page inspected was confirmed blank
and one number, pagesNeedingOcr, with ocrCountConfirmed telling you whether that number was measured on every page or partly inferred because the probe budget ran out.
What else comes back
Can it even be opened
Encrypted-with-a-password files, damaged files, and HTML error pages served with status 200 under a .pdf URL are separated from each other rather than all failing the same way.
Permissions Which of the eight PDF permission bits are denied: printing, copying text, editing, filling forms, extracting for accessibility, assembling, high-quality printing. A file that opens fine can still be one that your extractor refuses to read.
What the file discloses
Author, subject, keywords, creator, producer, creation and modification dates, XMP creator tool and document ID. Plus a byte-level search for local filesystem paths - C:\Users\..., /Users/..., \\fileserver\..., file:///... - that ended up baked into the file. Those name a person or map your internal storage.
revisions counts incremental saves. A file saved incrementally three times still contains the two earlier versions of its content; anything "removed" in the last pass may still be in there.
What it will run
Document-level JavaScript, OpenAction, Launch actions (which ask the reader to execute an external file), SubmitForm actions (which post entered data to a URL), embedded file attachments with their filenames, and rich media. For inbound documents from outside your organisation, these are the fields worth a look.
Embedded XML matching a known e-invoice profile - Factur-X, ZUGFeRD, XRechnung, Order-X, UBL - is called out separately, because there the attachment is the point of the file.
Conformance and accessibility
Tagged or not (from the document's own MarkInfo, not a guess), document language, title, bookmark count, PDF/A-1b-style conformance and PDF/UA-1 claims read from XMP.
Fonts Font names, and whether font programs are embedded. The warning only fires when a font outside the standard 14 is referenced without shipping the program - a file that uses only Helvetica and Times and embeds nothing is correct and portable, and is not reported as a problem.
Links inside the document
Every URI link annotation, with its page number. Relative links are resolved against the location the PDF was downloaded from, exactly as a reader would. file:// links and plain http:// links are flagged. Turn checkLinks on and each one is requested, so you get the dead ones.
Status, and why ok still means something
Every row carries status, one of ok, warning, error, plus issues (machine-readable codes), issuesBySeverity, and explanations - one plain sentence per issue, so nothing needs looking up.
The severity split is deliberate:
- error - the file could not be read, so no other field on that row can be trusted
- warning - something that will break a downstream job or leak information
- notice - true of most PDFs ever published: untagged, not linearised, producer string present, no document language
Notices are recorded, because if you are auditing for accessibility they are the whole point. They never change status. Untagged PDFs are the overwhelming majority of what exists; if that alone flipped every row to "warning", the twelve files that actually need work would be invisible.
onlyIssues uses the same rule, so it keeps the batch short instead of returning everything.
Output
One dataset row per PDF, plus four ready-made views in the Apify UI: Overview, OCR triage, Security and privacy, Accessibility and conformance. Export as JSON, CSV, or Excel.
A SUMMARY record in the key-value store holds the run totals: the status split, the textLayer split, how many pages out of the batch need OCR and which files are worst, the security counts, and a breakdown of every issue code by how often it fired.
Input
| Field | Default | Notes |
|---|---|---|
pdfUrls | - | Direct links, one per line. Redirects followed |
extractText | false | Also return the readable text |
checkLinks | false | Request every link found inside the documents |
onlyIssues | false | Drop rows whose status is ok |
maxPages | 200 | Page-level inspection budget per file |
maxOcrProbePages | 25 | Pages to read drawing instructions for |
maxFileMb | 50 | Refused before the whole file is downloaded |
maxConcurrency | 5 | Parsing holds a whole file in memory |
Limits, stated plainly
- Scanned-page detection is a measurement, not OCR. It tells you which pages need OCR and roughly what that will cost you. It does not read them.
- Password-protected files stay closed. No password input, no cracking. They come back as
encrypted_password_requiredand nothing else about them is claimed. - JavaScript,
Launchand attachment detection is best-effort. From PDF 1.5 onward, objects can live in compressed streams where a byte-level search cannot see them. Every positive is real; a negative means "not found", not "definitely absent". The parser's own findings are used first and the byte scan only adds to them. fontObjectscounts what is visible in the clear. In a file that stores its font dictionaries in compressed object streams the count is 0 even though fonts exist. The unembedded-font warning depends on both signals, so it stays silent rather than firing wrongly.- Digital signatures are reported, not validated. The Actor tells you a signature field exists. It does not tell you the signature is good.
- Only the pages inside
maxPagesare classified.pageCountis always the real total.
Verification
npm test runs 22 unit tests covering page classification, the PDF date format, XMP in all three encodings real tools produce (single-quoted attributes, double-quoted attributes, child elements) under both the xmpMM and xapMM prefixes, the byte scan, link classification, and the severity rules.
npm run test:live runs 45 checks against real files on the real internet: a scanned six-page document, a password-protected file, a PDF/A-1b file written by Ghostscript, a PDF/UA-1 tagged government form with an XFA layer, a file with an embedded attachment, a file containing a relative link, an HTML page served as a PDF, a 404, a real 301 redirect, the size limit, the timeout, and live link checking against known-good and known-dead URLs.