How to Build a PDF Summarizer with Claude
Building a PDF summarizer with Claude comes down to three steps: extract the text from the PDF, send that text to Claude with a well-structured prompt, and handle documents that are too long to fit in a single request. None of this requires a specialized ML pipeline — Claude does the heavy lifting as long as you feed it clean text and ask the right questions.
This guide walks through a working implementation you can adapt for contracts, research papers, reports, or any document-heavy workflow, including how to deal with PDFs that exceed Claude's context window.
Step 1: Extract text from the PDF
Claude doesn't read raw PDF bytes — you need to convert the file to plain text first. In Node.js, pdf-parse is a reliable, dependency-light choice:
import fs from "fs";
import pdf from "pdf-parse";
const buffer = fs.readFileSync("./report.pdf");
const data = await pdf(buffer);
const text = data.text;
For scanned PDFs (images rather than embedded text), you'll need OCR first — tesseract.js or a cloud OCR service — before this pipeline works. If data.text comes back mostly empty or garbled, that's your signal the PDF is image-based.
Clean up the extracted text before summarizing. PDF extraction often introduces broken line breaks, repeated headers/footers, and page numbers mid-sentence. A simple pass of regex normalization (collapsing whitespace, stripping repeated patterns) improves summary quality noticeably because Claude spends fewer tokens parsing noise.
Step 2: Chunk long documents
Claude's context window is large, but not infinite, and very long documents (100+ pages) can still exceed practical limits or make summarization less precise because important details get diluted. The standard approach is to split the document into chunks, summarize each chunk, then summarize the summaries.
function chunkText(text, maxChars = 15000) {
const chunks = [];
let current = "";
for (const paragraph of text.split("\n\n")) {
if ((current + paragraph).length > maxChars) {
chunks.push(current);
current = "";
}
current += paragraph + "\n\n";
}
if (current) chunks.push(current);
return chunks;
}
Chunking by paragraph (rather than a hard character cut) avoids slicing sentences in half, which keeps each chunk coherent for summarization.
Step 3: Summarize each chunk
For each chunk, send a focused prompt asking for key points rather than a narrative recap — this makes the second-pass "summary of summaries" easier to compress cleanly:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 500,
"messages": [
{
"role": "user",
"content": "Summarize the key points of this document section in 5-8 bullet points. Preserve specific numbers, names, and dates exactly as written.\n\n<document>\n{{CHUNK}}\n</document>"
}
]
}'
Run this across every chunk, collect the bullet-point summaries, then run a final pass that merges them into one coherent summary:
const finalPrompt = `Combine the following section summaries into one cohesive summary of the full document. Organize by theme, not by section order. Keep it under 400 words.\n\n${chunkSummaries.join("\n\n")}`;
This two-pass structure scales well — a 200-page report and a 2-page memo use the same pipeline, just with a different number of chunks.
Step 4: Design the prompt for your use case
"Summarize this" is a weak prompt. Be explicit about format, length, and what matters:
{
"role": "user",
"content": "Summarize this document for a busy executive. Output format:\n1. One-sentence overview\n2. 3-5 key takeaways as bullets\n3. Any action items or deadlines mentioned\n\nIf the document contains financial figures, list them verbatim. Document:\n\n<document>{{TEXT}}</document>"
}
Locking the output format makes summaries consistent across documents, which matters if you're building a tool other people will use regularly rather than a one-off script.
Step 5: Wire it into an API
If you're building this as a feature in a product — a "summarize this upload" button, a Slack bot, an internal tool for a legal or research team — you need Claude access exposed as a stable HTTPS endpoint with API keys, usage tracking, and streaming for long outputs. SubToAPI turns your existing Claude access into exactly that: issue a sub_live_... key, call /v1/messages, and get streaming, tool use, and per-key usage metadata without managing your own proxy infrastructure. See the quickstart to get a key running in a few minutes, and the messages docs for the full request/response shape.
For summarizer workloads specifically, streaming matters: a 400-word final summary is quick, but if you're also streaming per-chunk progress back to a UI (so users see "summarizing section 3 of 12"), you want streaming enabled from the start rather than retrofitted later.
A minimal end-to-end flow
- Extract text (
pdf-parseor OCR for scanned docs) - Clean and chunk the text
- Summarize each chunk via Claude
- Merge chunk summaries into a final summary
- Return the result through your API layer, streamed if the frontend needs progressive updates
For a side project or internal script, you can skip the API layer entirely and call Claude directly. For anything customer-facing — multiple users, usage limits per team, billing by seat — an API gateway like SubToAPI's Solo, Team, or Scale plans saves you from building key management and rate limiting yourself. There's a free trial at signup if you want to test the pipeline above against a real endpoint.
questions
Can Claude summarize a PDF directly without extracting text first? No. Claude's Messages API takes text input, so you must convert the PDF to plain text (or OCR it if scanned) before sending it in a prompt.
How do I summarize a PDF longer than Claude's context window? Split it into chunks, summarize each chunk separately, then run a final pass that merges the chunk summaries into one cohesive summary. This also improves quality on very long documents by forcing the model to process smaller, focused sections.
What's the best prompt structure for consistent summaries? Specify the exact output format (sections, bullet counts, word limit) and tell Claude what to preserve verbatim, like numbers and dates. Vague prompts like "summarize this" produce inconsistent length and structure across different documents.