C.W.K.
Stream
Lesson 03 of 06 · published

PDF Input: Two Paths and When to Use Each

~14 min · pdf, document, files-api

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

Native PDF support

Claude can read PDFs directly when you send them as content blocks of type document. The model parses both text and visual elements (tables, figures). For most documents — contracts, papers, manuals — this is the path of least friction.

Files API is for reuse

If the same PDF feeds many turns, upload it once via the Files API and reference by file_id on every turn. Saves bandwidth and latency. The trade-off is the upload step and remembering to clean up old files.

OCR fallback for scanned PDFs

Born-digital PDFs work great natively. Heavily scanned PDFs (especially old contracts) sometimes fail text extraction — preprocess with Tesseract or a managed OCR service before sending, or use vision blocks page-by-page if the layout matters.

Principle: Native PDF for one-shot reads, Files API for reusable assets, OCR fallback for the legacy paper world.

Code

Native PDF document block·python
import base64, pathlib

pdf_b64 = base64.standard_b64encode(pathlib.Path("contract.pdf").read_bytes()).decode()

resp = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": "application/pdf",
                        "data": pdf_b64,
                    },
                },
                {"type": "text", "text": "List all parties named in the agreement."},
            ],
        }
    ],
)
Files API for repeated reads·python
uploaded = client.beta.files.upload(
    file=("contract.pdf", open("contract.pdf", "rb"), "application/pdf"),
)

# Subsequent turns reference by file_id only
for question in ["List the parties.", "Find termination clauses.", "Total fees over 24 months?"]:
    r = client.beta.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": [
                {"type": "document", "source": {"type": "file", "file_id": uploaded.id}},
                {"type": "text", "text": question},
            ],
        }],
        betas=["files-api-2025-04-14"],
    )
    print(question, "->", r.content[0].text[:120])

External links

Exercise

Take a PDF question-answering flow that re-uploads on every turn. Convert it to use Files API, then measure latency and tokens for a 5-turn session.
Hint
Files API also benefits from prompt caching on the surrounding text — consider both together.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.