Stirling-PDF OCR, tested on scanned PDFs
401
first answer from the OCR API
Fix the 401 with an API key
A plain docker run of Stirling-PDF 3.3.0 turns login on. My first OCR call, with no key, got HTTP 401:
{"error":"Authentication required"}
I saved this as key.sh. It logs in as the default admin and prints the key:
JWT=$(curl -s -X POST "$STIRLING/api/v1/auth/login" -H "Content-Type: application/json" \
-d '{"username": "admin", "password": "stirling"}' | jq -r .session.access_token)
curl -s -X POST -H "Authorization: Bearer $JWT" "$STIRLING/api/v1/user/get-api-key" | jq -r .apiKey
Put the key in STIRLING_KEY, then change the default password,
stirling. SECURITY_ENABLELOGIN=false also worked with no
key, but then anyone who can reach the port can use it.
A scan works, typed page or not
1,857 of 1,955 true words found
12 s for 10 pages, on 2 CPUs
I saved this as ocr.sh and sent the 10-page scan:
curl -s -H "X-API-KEY: $STIRLING_KEY" -F "fileInput=@$1" -F "languages=${2:-eng}" \
"$STIRLING/api/v1/misc/ocr-pdf" -o "${1%.pdf}.ocr.pdf"
pdftotext scan.ocr.pdf - prints the text. With a typed
cover page in front, I got the same 1,857 words and 13 of 13 cover
words.
Stirling sends --skip-text, so OCRmyPDF reads only the
pages with no text.
Join languages with a comma
Tesseract joins languages with a plus. Stirling does not. With
languages=deu+eng, the API answered HTTP 400:
Invalid OCR languages format: none of the selected languages are valid
With deu,eng it ran OCRmyPDF with --language deu+eng.
Get clean text from the sidecar
On a two-column magazine page, the text layer of the output PDF mixed
the columns. Add sidecar=true, and the zip has the text
in reading order. I saved this as text.sh:
curl -s -H "X-API-KEY: $STIRLING_KEY" -F "fileInput=@$1" -F "languages=${2:-eng}" \
-F "sidecar=true" "$STIRLING/api/v1/misc/ocr-pdf" -o ocr.zip
unzip -p ocr.zip "*.txt"
| Setup | Words right | Umlauts right |
|---|---|---|
| eng, the default | 1,387 | 0 |
| deu, PDF text layer | 1,616 | 165 |
| deu, sidecar text | 1,843 | 192 |
| Our OCR Actor, deu | 1,853 | 192 |
Umlauts here are ä, ö, ü and ß. The Actor row is explained below.
A missing language fails quietly
The image has 5 OCR languages: chi_sim, deu, eng, fra and por. I asked for eng,ita on 3 Italian pages, and the API answered HTTP 200.
3 of 87 accents right
eng the only language OCRmyPDF got
Stirling drops a language it does not have, with no error. With ita alone, it answered the same HTTP 400 as above.
Add a language with a mount
Put the language file in a folder:
mkdir -p tessdata
curl -sL -o tessdata/ita.traineddata https://github.com/tesseract-ocr/tessdata_fast/raw/main/ita.traineddata
Then mount the folder at /usr/share/tessdata:
docker run -d --name stirling -p 8080:8080 \
-v "$PWD/tessdata:/usr/share/tessdata" stirlingtools/stirling-pdf:latest
79 of 87 accents right, with ita
1,301 of 1,350 words right
At start, Stirling copies the file next to its own 5. A mount over /usr/share/tesseract-ocr/5/tessdata hid those 5, and eng got HTTP 400.
Want Markdown out? Use the Actor
Stirling OCR is free and good. Our OCR Actor gives Markdown, in 10 languages, with no server. On the German pages:
curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/paperless-german-3-pages.pdf"], "language": "deu"}' \
| jq -r '.[].markdown' > german.md
899 of 915 words on the one-column pages, sidecar 900
954 of 972 on the two-column page, sidecar 943
Build 0.1.65, live since Nov 11, reads the two columns in order. Build 0.1.64 mixed them and got 497. It costs $0.006 a page.
How I tested it
- Scan: the 10-page PDF of OCR in n8n, proofread Wikisource book scans with no text layer. 1,955 true words.
- Typed page: a 13-word cover sheet in front of the scan, the file of AnythingLLM OCR.
- German: the 3 pages of Paperless-ngx OCR. Italian: 3 proofread it.wikisource pages from the Tesseract Italian test.
- Score: true words found in order, with the scorer of the tessdata test. PDF text layer: pdftotext.
- Stirling-PDF 3.3.0 on arm64, with Tesseract 5.3.4 and OCRmyPDF 17.13.0. A new container for each setup, 2 CPUs.
- The code blocks above are the files that ran. run.sh ran with the port on 127.0.0.1. The Actor: one real run on build 0.1.64, then the same input on build 0.1.65.
What I did not test
- The web page, pipelines, and the async job mode.
- The fat image, deskew, clean, and force-ocr.
- Handwriting, tables, and Stirling-PDF 2.x.
Sources
Two ways to skip the work
Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.
Questions people ask
Why does the Stirling-PDF OCR API return 401?
In 3.3.0, a plain docker run turns login on. Log in as admin, get the key from /api/v1/user/get-api-key, and send it as X-API-KEY. Or set SECURITY_ENABLELOGIN=false on a closed network.
How do I add an OCR language to Stirling-PDF?
Put the .traineddata file from tessdata_fast in a folder and mount it at /usr/share/tessdata. At start, Stirling copies it next to its 5 built-in languages.
Why does OCR answer 403 in Stirling-PDF?
The latest-ultra-lite image has no Tesseract and no OCRmyPDF, so its OCR call answered 403 Forbidden. Use the latest tag.
Related guides
- Docspell OCR, tested on scanned PDFs
- Papermerge OCR, tested on scanned PDFs
- OCRmyPDF language: set -l, or lose the accents
- Tesseract and Italian: eng reads no à, è, ì, ò or ù
- Paperless-ngx OCR, tested on scanned PDFs
- Nextcloud OCR, tested on scanned PDFs
- OCRmyPDF "page already has text": which flag to use
- How to make a PDF searchable, in one command
- A PDF OCR API in one curl call