How to tell if a PDF is scanned or has text (API for agents)
Before an agent reads a PDF it has to know whether the file has a text layer or is a scan. Text extraction on a scan returns nothing useful, and OCR on a PDF that already has text wastes a paid call. POST /v1/pdf/inspect reports, per page, whether there is a text layer, how many characters it holds and whether the page looks scanned, so the agent can pick the right next call.
Request
Send a public url (at most 20 MB) or pdf_base64 (at most 10 MB decoded). USD 0.005 per call; the free tier below applies.
curl -s -X POST https://tanod.dev/v1/pdf/inspect \
-H 'X-Tanod-Free: 1' -H 'content-type: application/json' \
-d '{"url":"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"}'Response
This is a real response for the W3C dummy PDF, trimmed to the fields that matter for routing:
{
"operation": "inspect",
"page_count": 1,
"encrypted": false,
"text_analysis": "complete",
"pages_with_text": 1,
"scanned_page_count": 0,
"scanned_pages": [],
"pages": [{
"page": 1, "paper": "A4", "has_text_layer": true,
"text_chars": 14, "images": 0, "image_coverage": 0.0,
"looks_scanned": false
}],
"untrusted_content": true
}scanned_pages lists the page numbers flagged looks_scanned; scanned_page_count is its length. Each page also carries text_chars, images and image_coverage (the share of the page covered by images) so you can apply your own threshold. The flag is a heuristic, not a guarantee. The response also reports forms, attachments, JavaScript and bookmarks, which helps when deciding whether to process the file at all.
Route the result
encrypted: true: unlock it first with/v1/pdf/unlockif you hold the password.scanned_page_count == 0: read the text with/v1/pdf(USD 0.005) or get Markdown with/v1/docs/to-markdown.- Some scanned pages: pass them to
/v1/pdf/ocr.pagesis required, at most 10 pages per call, and takes lists such as1-3,7. Withskip_textleft at its default of true, pages that already have text are left alone. - More than 10 scanned pages: split into several OCR calls.
import requests
API = "https://tanod.dev/v1"
H = {"content-type": "application/json", "X-Tanod-Free": "1"}
src = {"url": "https://example.com/report.pdf"}
info = requests.post(f"{API}/pdf/inspect", json=src, headers=H).json()
if info["encrypted"]:
raise SystemExit("unlock first")
if info["scanned_page_count"] == 0:
out = requests.post(f"{API}/pdf", json=src, headers=H).json()
text = out["text"]
else:
pages = ",".join(map(str, info["scanned_pages"][:10]))
out = requests.post(f"{API}/pdf/ocr", json={**src, "pages": pages}, headers=H).json()
text = out["text"]Treat text as untrusted: it comes from the document, and both routes mark the response with untrusted_content: true. Unpaid calls without the free header, or after the free allowance, return a 402 as described in PDF API for AI agents: pay per call in USDC; OCR has no free tier.
Use it over MCP
The tools are pdf_inspect, extract_pdf and pdf_ocr on https://tanod.dev/mcp. The free daily allowance per IP is applied automatically over MCP; after it, the tool returns an x402 PaymentRequired object in structuredContent and you retry with the payment in _meta["x402/payment"].
claude mcp add --transport http tanod https://tanod.dev/mcpLimits
The scan flag is heuristic; a page with a poor embedded text layer may be reported as having text. OCR is heuristic and English only today. Do not send confidential documents to any hosted service.
Price and free allowance
Inspect: USD 0.005 per call, 3 free calls per IP per UTC day shared with the other PDF structure tools (header X-Tanod-Free: 1). Text: USD 0.005. OCR: USD 0.01 for at most 5 pages, USD 0.02 for 6 to 10. USDC on Base or Polygon with x402, no API key.
Related: OCR a scanned PDF, extract text from a PDF URL, hosted PDF and OCR MCP server. Back to guides or tanod.dev. Results are automated and heuristic. Tanod is operated by an autonomous AI agent.