Python's parser zoo, replaced by one POST.

pypdf for PDFs, python-docx for Word, openpyxl for Excel, extract-msg for Outlook, pytesseract for scans — five libraries, five APIs, five sets of edge cases. txtfetch is one.

the-parser-zoo

Python has the deepest document-parsing ecosystem of any language, which is exactly the problem: covering PDF, Office, email, and scans means installing and maintaining five separate libraries, each with its own API and its own blind spot. pypdf reads digital-native PDF text cleanly but has no OCR path and struggles with multi-column layouts and dense tables. python-docx is OOXML-only — .docx works, but a .doc from 2009 throws before you get a single character back, and it has nothing at all for .pptx or .xlsx. openpyxl reads .xlsx cells directly, but formula cells need data_only=True to get the calculated value instead of the =SUM() string, and that cached value only exists if the file was last saved by Excel itself — a workbook produced by another tool can hand back None. And pytesseract isn't really an OCR library — it's a thin wrapper that shells out to a system tesseract binary you have to install and keep on PATH yourself, plus Pillow for images and pdf2image (which itself needs poppler-utils) if the scan is a PDF rather than a bare image.

librarycoversstops at
pypdfDigital-native PDF text extractionNo OCR; struggles with multi-column layouts and dense tables
python-docx.docx paragraphs, tables, and headersOOXML only — no legacy .doc, and nothing for .pptx or .xlsx
openpyxl.xlsx cell values, including shared stringsFormula cells need data_only=True plus a prior Excel save — no live recalculation; no .xls
extract-msgOutlook .msg header and body parsingOnly .msg — no .pst/.ost mailbox archives, and nothing outside email
pytesseractOCR, once paired with Pillow and a system Tesseract installYou install, update, and package the tesseract binary yourself; PDFs need pdf2image + poppler on top

one-request

txtfetch replaces the imports, not just the parsing. One POST — multipart file or ?url= — returns { "status": "success", "extracted_text": "..." } whether the source was a pypdf-shaped PDF, a python-docx-shaped .docx, an openpyxl-shaped .xlsx, or a pytesseract-shaped scan. The official txtfetch Python SDK wraps the same call in a typed client with exceptions per error code, so a try/except replaces the format-detection branching a hand-rolled pipeline needs before it can even pick which library to call.

Python
import os
import requests

with open("report.pdf", "rb") as f:
    r = requests.post(
        "https://api.txtfetch.com/v1/extract",
        headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
        files={"file": f},
    )

print(r.json()["extracted_text"])
{
  "status": "success",
  "extracted_text": "..."
}

txtfetch (Python SDK)

pip install txtfetch — one client class, typed exceptions per error code, no per-format branching.

Install
pip install txtfetch
Quickstart
from txtfetch import Txtfetch

# api_key defaults to the TXTFETCH_KEY environment variable
client = Txtfetch(api_key="tf_live_...")

# Extract from a local file (path, bytes, or a file-like object all work)
result = client.extract(file="whitepaper.pdf")
print(result.extracted_text)
print(result.metadata.content_type, result.metadata.bytes, result.metadata.ocr)

# Extract from a URL — txtfetch fetches it server-side
result = client.extract(url="https://example.com/whitepaper.docx")

from-a-url

Skip the download entirely — pass a url parameter and txtfetch fetches the document server-side:

Python
import os
import requests

r = requests.post(
    "https://api.txtfetch.com/v1/extract",
    headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
    params={"url": "https://example.com/report.pdf"},
)

print(r.json()["extracted_text"])

errors

Every non-success response carries a stable error.code — match on that, not on error.message. See the full error reference for every code and HTTP status txtfetch can return.

Python
import os
import requests

with open("report.pdf", "rb") as f:
    r = requests.post(
        "https://api.txtfetch.com/v1/extract",
        headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
        files={"file": f},
    )

if r.status_code == 200:
    print(r.json()["extracted_text"])
elif r.status_code == 429:
    code = r.json()["error"]["code"]  # "rate_limited", "quota_exceeded", or "abuse_detected"
    print(f"back off: {code}, retry after {r.headers['Retry-After']}s")
else:
    error = r.json()["error"]
    print(f"extraction failed: {error['code']} — {error['message']}")

big-files-and-batches

Large uploads or slow documents are routed to an async job automatically — 202 plus a job_id to poll — and ?async=true forces that path for any request. Direct upload size ceilings by plan: Hobby 10 MB, Developer 50 MB, Scale 200 MB. Use ?url= for anything larger — server-side fetches aren't held to the upload ceiling. See async jobs & webhooks for the full lifecycle, including webhook delivery instead of polling.

Python
import os
import time
import requests

HEADERS = {"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"}

submit = requests.post(
    "https://api.txtfetch.com/v1/extract",
    headers=HEADERS,
    params={"url": "https://example.com/report.pdf", "async": "true"},
)
job_id = submit.json()["job_id"]

while True:
    poll = requests.get(f"https://api.txtfetch.com/v1/extract/{job_id}", headers=HEADERS)
    result = poll.json()
    if result["status"] != "processing":
        break
    time.sleep(2)

print(result["extracted_text"])

gotchas

  • pip install pytesseract installs a Python wrapper, not an OCR engine — without the actual tesseract binary on PATH, every call fails at runtime with "tesseract is not installed or it's not in your PATH", not at import time.
  • openpyxl's data_only=True reads Excel's last cached calculation, not a live recalculation. A workbook written by a script that never opened it in Excel can have an empty cache, so a perfectly valid formula cell reads back as None.
  • pandas.read_csv guesses UTF-8 by default; a Windows-1252 export with curly quotes or em dashes mojibakes silently unless you pass the correct encoding= yourself.
  • python-docx, python-pptx, and openpyxl are three separate PyPI packages with three separate object models for what marketing calls "just Office files" — there's no single import that spans the family.

formats

faq

How do I extract text from a PDF in Python without pypdf or pdfplumber?
POST the file to https://api.txtfetch.com/v1/extract (multipart, or ?url= for a remote PDF) with your API key in the Authorization header, or use the txtfetch Python SDK's client.extract(file="..."). You get back { "status": "success", "extracted_text": "..." } whether the PDF is digital-native or scanned — no pypdf/pdfplumber import, and no separate OCR branch to write.
Can I OCR a scanned document in Python without installing Tesseract myself?
Yes — POST the scan (PNG, JPG, TIFF, or a scanned PDF) to txtfetch and OCR runs server-side automatically. There's no tesseract binary to install, no PATH to configure, and no pytesseract/Pillow/pdf2image chain to keep working across OS upgrades.
How do I read an Outlook .msg file in Python?
extract-msg works but only covers .msg. txtfetch's Python SDK handles .msg the same way as every other format — client.extract(file="thread.msg") — and also covers whole .pst/.ost mailbox archives through the same call, which extract-msg doesn't.
Does openpyxl's data_only=True always give me the calculated cell value?
Only if the workbook was last saved by Excel itself — data_only=True reads a cached result, and that cache can be empty for files produced by other tools. txtfetch returns the same cached calculated value with no data_only= flag to remember and no separate code path per format, but it reads Excel's cache too: a workbook whose formulas were never calculated by a spreadsheet app has no stored result for either tool to find.
Is there an official Python SDK for txtfetch?
Yes — pip install txtfetch. Its only runtime dependency is httpx, and it maps every API error code to its own typed exception class (see /docs/quickstarts).

go-further

Stop parsing. Start shipping.

Create an account and get an API key in minutes — the free Hobby plan needs no card.

Get started →