Claude API Summarization: Long Documents Tutorial
How to summarize long documents with the Claude API
Summarizing a 50-page contract, a research paper, or a year of meeting notes with an LLM sounds simple until you hit the context window limit or the model starts dropping details from the middle of the text. This tutorial walks through the practical approach: when you can send a document in one call, when you need to chunk it, and how to structure prompts so the summary stays accurate and complete.
The short answer is this: Claude's large context window (up to 200K tokens depending on the model) means most documents — even long ones — fit in a single request. For anything beyond that, or when you need section-level control, you chunk the document, summarize each piece, then run a second pass that merges those summaries into one coherent output. This is the "map-reduce" pattern, and it's the standard technique for summarizing long documents with any LLM API.
Step 1: Try a single-call summary first
Before building a chunking pipeline, check if the document actually needs one. Claude models can handle very large inputs directly, so a single well-structured prompt is often enough:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Summarize the following document in 5 bullet points, preserving all dates, figures, and named entities:\n\n<document text here>"
}
]
}'
This works well up to the model's context limit minus your expected output size. The failure mode to watch for isn't length — it's recall. Even within the context window, models can under-weight content buried in the middle of a very long input. If your document is dense with discrete facts (clauses, line items, timestamps), it's safer to chunk regardless of whether it technically fits.
Step 2: Chunk the document
Split by logical boundaries, not arbitrary character counts. Headings, paragraphs, or page breaks work better than fixed-size slicing because they keep related sentences together.
function chunkByParagraphs(text, maxCharsPerChunk = 12000) {
const paragraphs = text.split(/\n\s*\n/);
const chunks = [];
let current = "";
for (const p of paragraphs) {
if ((current + p).length > maxCharsPerChunk) {
chunks.push(current);
current = p;
} else {
current += "\n\n" + p;
}
}
if (current) chunks.push(current);
return chunks;
}
A rough rule: aim for chunks of 8,000–15,000 characters. That leaves plenty of room for instructions and output tokens while keeping each chunk coherent enough to summarize on its own.
Step 3: Map — summarize each chunk
Run each chunk through the same summarization prompt, independently. Ask for a structured output (bullets, not prose) so the merge step in the next stage has clean material to work with:
async function summarizeChunk(chunk, index) {
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 500,
messages: [{
role: "user",
content: `Summarize section ${index + 1} in 3-5 bullet points. Keep all numbers, names, and dates exact.\n\n${chunk}`
}]
})
});
const data = await response.json();
return data.content[0].text;
}
Run these in parallel with Promise.all to keep latency reasonable — a 20-chunk document doesn't need to take 20x as long as a single call.
Step 4: Reduce — merge the summaries
Concatenate the chunk summaries and send them back to Claude for a final pass that produces one coherent summary, removes redundancy, and reorders points logically:
async function mergeSummaries(chunkSummaries) {
const combined = chunkSummaries.join("\n\n");
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1000,
messages: [{
role: "user",
content: `Below are section summaries from one document. Merge them into a single coherent summary of 8-10 bullet points, removing redundancy but keeping all distinct facts:\n\n${combined}`
}]
})
});
const data = await response.json();
return data.content[0].text;
}
This two-stage pipeline scales to documents of almost any length — legal filings, transcripts, codebases, research corpora — because each stage stays within a manageable token budget regardless of total document size.
Choosing between single-call and map-reduce
- Single call: fastest, cheapest, and accurate enough for documents under ~100K tokens with low information density (narrative text, reports with a clear thesis).
- Map-reduce: necessary for very long or very dense documents, or when you need guarantees that every section was actually read — not just statistically likely to be attended to.
A hybrid works well in practice: split into fewer, larger chunks (3–5 instead of 20+) to reduce the number of API calls while still getting the recall benefits of chunking.
Production considerations
Once this pipeline is running in an app rather than a script, you'll want application-level API keys instead of sharing one raw key, streaming for long merge outputs so users see progress instead of staring at a spinner, and usage metadata to know what summarization is costing you per document or per customer.
SubToAPI turns your Claude access into an HTTPS API with exactly that: issue scoped sub_live_... keys per app or team, stream responses token by token (see /docs/streaming), and get usage breakdowns in a dashboard instead of digging through raw logs. The request format mirrors Claude's Messages API directly, so the code above works with a one-line base URL change — check /docs/messages for the full reference or /docs/quickstart to get a key running in minutes. Plans start at Solo for €9/month, with Team and Scale tiers for shared seats; see /pricing or start a free trial at /signup.
questions
How long can a document be before I need to chunk it for summarization? If it fits comfortably within the model's context window minus expected output tokens, a single call usually works. For dense documents with many discrete facts, chunk even if it technically fits — recall degrades for buried details well before the hard token limit.
Does map-reduce summarization lose information compared to a single call? Done correctly, no — each chunk is summarized independently so nothing is skipped, and the merge pass only removes redundancy, not content. The main risk is over-aggressive merging; ask the model explicitly to preserve all distinct facts.
Can I stream the final summary instead of waiting for the full response? Yes. Streaming is especially useful for the merge step, where output can run several hundred tokens — see /docs/streaming for how to handle streamed responses in your summarization pipeline.