Extract text from any PDF, one request.
Multi-column academic papers, dense financial tables, scanned contracts — one endpoint, one response shape, no per-layout tuning.
the-problem
PDFs are the least standardized 'standard' in document formats: multi-column layouts that confuse naive text extraction, tables that collapse into unreadable strings, and scanned pages with no text layer at all. Most teams end up bolting together a PDF library for the easy cases and a separate OCR pipeline for the scanned ones — two code paths to maintain, two sets of edge cases to debug. VLM-based extractors that read pages as images tend to do fine on a single clean scan but struggle with mixed-format batches — a folder with digital-native reports next to faxed scans breaks the assumption that every page is a picture.
one-request-solution
txtfetch takes the PDF — digital-native or scanned, doesn't matter — and always gives back the same JSON shape. Text-layer pages go straight through Apache Tika; pages with no text layer route through Tesseract OCR automatically, in the same request. One code path for every PDF in your pipeline.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@quarterly-report.pdfimport os
import requests
with open("quarterly-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"])import { readFile } from "node:fs/promises";
const file = new Blob([await readFile("quarterly-report.pdf")]);
const form = new FormData();
form.append("file", file, "quarterly-report.pdf");
const res = await fetch("https://api.txtfetch.com/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
body: form,
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
f, err := os.Open("quarterly-report.pdf")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "quarterly-report.pdf")
if err != nil {
panic(err)
}
if _, err := io.Copy(part, f); err != nil {
panic(err)
}
writer.Close()
req, err := http.NewRequest("POST", "https://api.txtfetch.com/v1/extract", &body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}Or skip the download — pass a url parameter and txtfetch fetches the document server-side:
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/whitepaper.pdf" \
-H "Authorization: Bearer $TXTFETCH_KEY"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/whitepaper.pdf"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/whitepaper.pdf");
const res = await fetch(endpoint, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
endpoint, err := url.Parse("https://api.txtfetch.com/v1/extract")
if err != nil {
panic(err)
}
q := endpoint.Query()
q.Set("url", "https://example.com/whitepaper.pdf")
endpoint.RawQuery = q.Encode()
req, err := http.NewRequest("POST", endpoint.String(), nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}{
"status": "success",
"extracted_text": "..."
}formats-covered
.pdf
faq
- How do I extract text from a PDF?
- POST the file as multipart form data to https://api.txtfetch.com/v1/extract, or pass a url parameter and txtfetch fetches it server-side. Either way you get back { "status": "success", "extracted_text": "..." }.
- Does it handle scanned PDFs, not just digital-native ones?
- Yes. Pages with no text layer route through Tesseract OCR automatically — same request, same response shape. You don't need to detect or flag scanned pages yourself.
- What about multi-column layouts and tables?
- Apache Tika parses the underlying PDF structure rather than guessing from pixel positions, so multi-column academic papers and tabular financial reports come out as readable, ordered text.
- Is there a page limit?
- No. One extraction request is one document, regardless of length — a 300-page PDF still counts as a single request.
go-further
PDF & scans
Stop parsing. Start shipping.
Create an account and get an API key in minutes — the free Hobby plan needs no card.
Get started →