Tools,

OCR a folder of scanned documents in one run

$0.006

a page: PDF, TIFF or photo

Try it on Apify

Folder in, text out

Set APIFY_TOKEN and put your files in scans. Each scan with text becomes a Markdown file in text.

pip install apify-client
import mimetypes, os, re
from pathlib import Path
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
store = client.key_value_store(client.key_value_stores().get_or_create().id)
files = {}
try:
    scans = [f for f in sorted(Path("scans").iterdir()) if f.is_file() and f.name[0] != "."]
    for i, f in enumerate(scans):
        key = f"{i}-" + re.sub(r"[^A-Za-z0-9._()'!-]", "-", f.name)
        store.set_record(key, f.read_bytes(), content_type=mimetypes.guess_type(f.name)[0])
        files[store.get_record_public_url(key)] = f.name
    run = client.actor("brasspage/pdf-ocr-to-markdown").call(
        run_input={"documentUrls": list(files)}
    )
    print("Run", run.status)
    Path("text").mkdir(exist_ok=True)
    for doc in client.dataset(run.default_dataset_id).iterate_items():
        name = files.get(doc.get("url"))
        if doc.get("markdown"):
            Path("text", name + ".md").write_text(doc["markdown"], encoding="utf-8")
        print(name, doc.get("pagesConverted"), "of", doc.get("pageCount"), doc.get("error") or doc.get("note") or "")
finally:
    store.delete()

One real run: 3 files, 8 pages

I ran that exact script on Nov 7, 2026, on three public files. It skipped the hidden .DS_Store that Finder leaves in folders.

Tesseract's test scan: black typed text on white, a short line about testing OCR and four lines of the quick brown dog sentence
phototest.tif, Tesseract's own test scan (Apache-2.0).
File Read from Result
phototest.tif OCR 60 of 60 words
A made-up invoice scan, JPG OCR 33 of 33 numbers
The IRS W-9, 6 pages Its text layer The PDF's own text
Pages
8
Seconds, start to last file
22
Price of this run
$0.04805

It printed 6 of 6, 1 of 1 and 1 of 1: pages read, of pages in the file. A lower first number means a blank or failed page.

What happens to each page

  1. A page with real text is read as it is, so the characters are exact.
  2. A scanned page, photo, fax or image is rendered at 300 dpi, turned upright, deskewed and cleaned, then read by Tesseract 5.
  3. If Tesseract reads an English page badly, RapidOCR reads it too. Its text is used if it finds clearly more.
  4. A PDF that another tool already OCRed keeps that tool's hidden text, errors and all. If that text is tilted more than 0.8 degrees, the page is read again.
  5. A file that fails, like a password-protected PDF, gets an error and no charge. The other files carry on.

Good at typed pages, weak at handwriting

From the Actor's README, build 0.1.44
Pages Result
Book scans, 60 pages, 5 handwritten 96% of words
Turned pages, 306 test copies 306 of 306 upright
Scanned invoices, 24 pages, 4 fonts 810 of 810 numbers
Scanned tables with no grid lines 74% of cells right
Phone photos of whole pages 81% of words
Receipt photos 67% of words
Handwritten notes 52% of words

Check these yourself: handwriting, phone photos, and pages with 2 or 3 text columns, where the first lines can mix the columns.

Big folders: four limits

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Do my scans go to an LLM?

No. Open source OCR runs inside the Actor: Tesseract, plus RapidOCR for pages that Tesseract reads badly. The script deletes its upload store at the end, and the text stays in your own Apify storage.

Can I get plain text instead of Markdown?

Yes. Add "outputFormat": "text" to run_input, and change both "markdown" keys in the loop to "text".

Which languages does it read?

English by default. For Spanish, add "language": "spa" to run_input, or fra, deu, por, rus, ara, hin, chi_sim or jpn for French, German, Portuguese, Russian, Arabic, Hindi, Chinese (simplified) or Japanese. One run reads one language, and we have no accuracy number for other languages.

Do I need Python?

No. In the Apify Console, paste file links into documentUrls, or upload one file. From other code, see the PDF OCR API.

Where do these numbers come from?
  • The run: IV2m9gomOTlhSBSew on build 0.1.44, the live build, at the default 1 GB. Apify timed the run itself at 13.3 seconds.
  • The price uses the Store prices: one start at $0.00005 and 8 pages at $0.006. As the Actor's owner, I was not charged.
  • The 60 words match phototest.txt, the truth file next to the scan. The invoice output matches the README's own run.
  • The README rows: the invoices and tables are made-up test files. The photo, receipt and note rows are real photos from GNHK, CORD and SmartDoc 2015.