OCR scanned documents and images through one API call
A large share of “documents” in any real intake pipeline aren’t documents in the structured sense at all — they’re images. A scanned invoice from a supplier who still uses a fax-to-email service. A photographed receipt from an expense app. A signed contract that was printed, signed, and re-scanned because someone didn’t trust the e-signature. All of them arrive as PNGs, JPEGs, or image-only PDFs with zero extractable text.
If your extraction pipeline is built around a text-layer library —
pdftotext, python-docx, anything that reads structured content — these
files return nothing. Not an error, often just an empty string, which is the
worst possible failure mode because it looks like success.
Why OCR needs to be a first-class path, not a special case
The usual fix is bolting Tesseract onto the side of a pipeline: detect that a file is an image, route it to a separate OCR step, merge the result back in. That’s a second dependency to install, a second set of language packs and model files to keep updated, and a second code path that inevitably drifts out of sync with whatever error handling and retry logic the “real” text extraction path has.
It also pushes a classification problem onto you: is this PDF text-based or scanned? Some are both — a scanned cover page stapled to a native PDF body. Get the routing wrong and you either waste a Tesseract call on a document that didn’t need it, or silently skip OCR on a page that did.
One endpoint, format detection included
txtfetch runs Tesseract as a Lambda layer alongside Apache Tika, and decides
which one a request needs by inspecting the actual bytes — not the file
extension, not a flag you have to set. A .jpg receipt, a scanned .pdf, and
a native .docx all go through the same call:
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@invoice-scan.jpg
Or, if the image is already sitting on a URL — say, an attachment in an inbound email pipeline:
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/receipts/inv-4471.png" \
-H "Authorization: Bearer $TXTFETCH_KEY"
The response shape doesn’t change based on whether OCR ran or the file had a native text layer:
{
"status": "success",
"extracted_text": "INVOICE #4471\nBill to: Acme Corp\nTotal due: $1,240.00"
}
Your downstream code — the code that reads extracted_text and does
something with it — never needs to know which path a given file took.
Handling OCR failures explicitly
OCR is probabilistic in a way native text extraction isn’t: a blurry photo or a low-contrast fax can legitimately fail to yield usable text. txtfetch surfaces that as an explicit error rather than an empty success:
{
"status": "error",
"error": "No text could be extracted"
}
That distinction matters in a pipeline. An empty string that reports
"status": "success" gets embedded and indexed like any other chunk — a
silent gap in your retrieval that nobody notices until a user asks about a
document that’s technically “in” the system. An explicit error is something
you can catch, log, and route to a re-scan queue or a human reviewer.
Where this fits
Scanned intake is rarely the whole pipeline — it’s the long tail: the
supplier who still faxes, the older records that predate digital
originals, the field team uploading phone photos of paperwork. Routing that
long tail through the same /v1/extract call as everything else means one
retry policy, one error format, one thing to monitor, instead of a
special-cased OCR branch that only gets tested when someone notices it’s
broken.
Have a batch of scanned documents you want to test against? Get in touch and we’ll set you up with an API key.