A dune coastline at sunset, the shoreline running away to a horizon you cannot see the end of. How far you get depends on what you are carrying, not on the map.

If you have sent a long PDF to the Claude API, you have probably met a 400 saying A maximum of 100 PDF pages may be provided.

Search for the fix and you get four different numbers. Anthropic’s older docs say 100 pages per request. The current platform docs say 600. AWS Bedrock says 100. Claude Code’s changelog says 100 pages and 20MB. None of those pages measured anything, so on 6 September 2026 I measured all of it against a 171-page Form 10-K.

The short version is that splitting the file is the wrong fix. For questions, search the PDF on disk and send only what matched. For whole-document tasks like summarisation, the answer is different: window the text and render only the pages that need vision.

TL;DR: Every PDF limit on the Claude API is a limit on the request, not the document, so the fix is to stop putting the document in the request. Search it on disk and send only the pages that matched. When the task has no query, window the text instead and render only the pages that are pictures. Claude Haiku 4.5 refuses a 171-page PDF outright, then answers two questions about that same file for $0.0155 once it fetches pages instead of receiving them. A full summary of the same file, charts included, costs $0.53.


How many PDF pages does the Claude API actually accept?

600 pages per request, or 100 when the model’s context window is under 1M tokens, inside a 32MB payload.

Both limits cover the whole request, not just the PDF. And on a text-dense document the input stops fitting a 1M context window at around 373 pages, well before the 600.

Those are the documented figures as of 6 September 2026, from the PDF support page. Anthropic moves them. Bedrock caps requests at 20MB and Google Cloud at 30MB.

The gap between 600 and 373 is not a documentation error so much as a missing multiplication: the page cap is documented in one place and the per-page token cost in another.


Why the documented 600-page limit isn’t really 600 pages

Three measurements, one argument: the cap you get depends on the model, a page costs more than the docs imply, and multiplying those two together lands well short of 600.

The 100-page cap belongs to the model, not the document

Here is that error in full, from my own run, sending the 171-page 10-K to Claude Haiku 4.5:

400 invalid_request_error
messages.0.content.0.pdf.source.base64.data:
A maximum of 100 PDF pages may be provided.

The same file, byte for byte, is accepted by Claude Opus 5.

The page cap is a property of the model you picked, not the document you sent. Haiku 4.5 has a 200K context window, which is under 1M, so it gets the 100-page cap. Opus 5 has 1M, so it gets 600.

The practical consequence is nasty. You build a pipeline on a large-context model, it works, and then someone swaps in a cheaper model to save money. The pipeline breaks with an error that says nothing about context windows and nothing about the model swap. It just says the document has too many pages, which is the one thing that did not change.

A page costs about 2,700 tokens, and most of it is an image

Every page is processed twice: the text is extracted, and the page is rendered to an image. You pay for both. The docs quote “1,500-3,000 tokens per page” for the text and mention image tokens separately without a number, so the figure people budget from is the text-only one.

Measured with count_tokens, which is free and runs no generation, on page-count prefixes of the same document:

Pages Input tokens Marginal per page
1 3,180 3,180
2 4,879 1,699
5 12,605 2,575
10 26,741 2,827
25 69,261 2,835
50 145,781 3,061
100 272,001 2,524
171 456,946 2,605

From page 5 on, the marginal cost sits between 2,500 and 3,050 across two orders of magnitude of document size. The first two pages are cheaper because a cover and a contents page carry little text. Over the whole document the average is 456,946 / 171, so call it 2,700 tokens per page.

Here is the part the docs do not tell you. Extract the text layer with pdftotext and count it the same way, and it comes to 192,070 tokens, or 1,123 per page. That is below the floor of the documented 1,500-3,000 band, not above it. The other 1,550 tokens per page are the rendered image, which is most of the bill. (Anthropic’s own extraction may differ a little from pdftotext’s, so treat the split as close rather than exact.)

It also explains an error claude.ai users hit constantly, which I covered in the pdf-mcp origin story: “each PDF page counts as one image.” That turns out to be a description of how PDF support works everywhere, not a quirk of the chat app.

So a dense document runs out of context at 373 pages

Multiply 2,700 tokens by 600 pages and you get 1.6M tokens against a 1M context window, so the two ceilings disagree. To find where the real one sits I built longer documents by repeating the 171-page filing’s pages until each reached the target count, which changes nothing about per-page cost:

Pages Input tokens Result
300 802,974 under 1M
372 997,614 under 1M
373 1,000,926 over 1M
600 1,608,628 over 1M, still under the page cap
601 rejected 400, page cap

count_tokens crosses 1M at 373 pages. Reaching 600 would need a document averaging under about 1,670 tokens per page, sparser than any filing, standard, or contract I have worked with. A picture book would manage it.

Be precise about what that table is, because I was not at first. Only the last row is an observed rejection. The rest are token counts, so 373 is where the input stops fitting a 1M window on paper, not a request I watched fail. count_tokens reports what the input costs, not whether the request will run: it returned 1,608,628 for the 600-page build without complaint.

And carry away the method rather than the figure, since 373 is this document’s density. Measure your own per-page cost, multiply by the page count, and compare that against the window before trusting the cap.


The fix depends on which task you have

Every limit above is a limit on the request, not on the document. The moment the PDF stops travelling inside the API call, all of them stop applying. There are two ways to keep it out, and the task decides which one you need, not the file.

long PDF
  |
  +-- have a question -> search, send the pages that matched
  |
  +-- need the whole document -> window text, render pictures

A question gives you something to search for, so retrieval works and costs almost nothing. Summarising, translating, or extracting every instance of something gives you nothing to search for, because the answer is the whole document. Most writing on long PDFs, including the first draft of this post, covers only the first branch and quietly presents it as the whole answer.


If you have a question: stop sending the document

Here is the same Haiku 4.5 that refused the 171-page file, answering questions about it. The document stays on disk and the model reaches it through pdf-mcp tools in an ordinary tool-use loop:

Q: Reality Labs' loss from operations in 2022?
   turn 1  pdf_search("Reality Labs loss from operations")
   turn 2  answer
   4,558 input tokens, 303 output, $0.00607

Q: Family of Apps revenue in 2022, and the page?
   turn 1  pdf_search("Family of Apps revenue segment")
   turn 2  pdf_read_pages("71")
   turn 3  answer
   8,233 input tokens, 239 output, $0.00943

Both answers were correct, and the two together cost $0.0155. The second one is the more interesting of the pair: the model decided the search excerpt was not enough, went and read the page, and cited it.

pdf-mcp is an MCP server, but its tools are plain Python functions, so a script can import them and skip running a server:

pip install pdf-mcp anthropic
import json

import anthropic
from pdf_mcp.server import pdf_read_pages, pdf_search

PDF = "meta-10k-2022.pdf"  # 171 pages, too many for Haiku
TOOLS = [
    {
        "name": "pdf_search",
        "description": "Search the PDF. Returns ranked pages.",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
    {
        "name": "pdf_read_pages",
        "description": "Read pages, e.g. '56' or '56-58'.",
        "input_schema": {
            "type": "object",
            "properties": {"pages": {"type": "string"}},
            "required": ["pages"],
        },
    },
]
IMPL = {"pdf_search": pdf_search, "pdf_read_pages": pdf_read_pages}

client = anthropic.Anthropic()
messages = [{"role": "user", "content":
             "What was Reality Labs' loss from operations in 2022?"}]

while True:
    reply = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=2048,
        tools=TOOLS,
        messages=messages,
    )
    if reply.stop_reason != "tool_use":
        print("".join(b.text for b in reply.content
                      if b.type == "text"))
        break

    messages.append({"role": "assistant", "content": reply.content})
    results = []
    for block in reply.content:
        if block.type != "tool_use":
            continue
        out = IMPL[block.name](path=PDF, **block.input)
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(out, default=str),
        })
    messages.append({"role": "user", "content": results})

Run it against the 171-page filing and Haiku answers:

Reality Labs' loss from operations in 2022 was $13,717
million. This represents a 35% increase compared to 2021,
when the loss was $10,193 million.

The path=PDF in the tool call is the whole trick. The model chooses the query and the page range; your code supplies the file. The PDF is read on your machine, and only the excerpt travels to the API.

The mechanism is unremarkable, which is why it works. Haiku’s 200K window and the 100-page rule are both exactly where they were. The document just stopped being part of the request, so the cap was never consulted. A 3,000-page document hits the API the same way, because what the model holds is a few thousand tokens of excerpt either way.

Compare the two paths end to end on the same question, counting every token billed across every turn:

  Whole document Search first
Model it runs on Opus 5 (Haiku 400s) Haiku 4.5
Input tokens 456,946 4,558
Turns 1 2
Cost $2.29 $0.006
Context consumed 46% of a 1M window 2% of a 200K window

That is 100 times fewer input tokens, and the interesting column is the first one. The direct path does not merely cost more, it obliges you to rent a 1M-context model, because the cheap one refuses the file. Retrieval removes that constraint before it removes the cost.

(Of those 4,558 tokens, the search result itself is 1,581. The rest is the tool definitions and the question, resent on the second turn, which is what makes the loop cost more than the payload.)

I would resist reading that as a pure cost optimization, though. The upload path has no retrieval step that can miss the answer; search does. You are trading a recall risk for a ceiling, and only one of those is negotiable. Past roughly 373 pages the direct path does not exist, so retrieval stops being the cheaper option and becomes the only one.

Which retrieval you use matters less than the ordering. Search to find the pages, then read only those. That sequence is one of five patterns for how agents should read PDFs, and on documents with clean tables of contents a section-based chunk beats a fixed page range.

All of which holds only while there is something to search for.


If you need the whole document: window the text

I wanted a summary of that same 171-page filing. Not an answer to a question about it, a summary: the business, the results, the segment split. Nothing in the section above helps, because there is nothing to search for and no page is safe to skip.

Needing the whole document is still not the same as sending it. Read the text layer off disk in windows, take notes on each window, then merge the notes. Seven windows of 25 pages covers the 171-page 10-K, and each request carries extracted text rather than a PDF.

This works, and it is cheap. The notes come back with the revenue figures, the Reality Labs loss, the layoffs, the litigation. Text extraction is good at prose, a 10-K is mostly prose, and the whole pass runs on Haiku 4.5, the model that refuses this file outright, for $0.21.

The interesting failure is elsewhere.


What the text layer quietly throws away

Page 61 of the filing carries a bar chart: nine quarters of revenue, each bar split into advertising and non-advertising, with a total printed above it. Here is that page’s entire text layer.

ARPP:
$8.62  $7.75  $8.36  $8.18  $9.39
$7.72  $7.91  $7.53  $8.63

Not one bar value. No series names. No quarter labels. The ARPP row survives only because it sits outside the plot area, and it arrives with its own column headings gone.

The data is not missing. Nine real numbers are right there. What is missing is every label saying which number belongs to which quarter, which is worse: a model asked what ARPP was in Q1 2022 has nine plausible values in front of it and nothing to tell it it cannot answer.

I measured how much worse. I ran eight questions about the charts on two pages three ways against Haiku 4.5. Half had answers that existed only inside the image; the other half were present in the text but stripped of their labels. As a control I asked each question with no document attached: all sixteen declined, so nothing below is recalled from training.

What the model got Correct Wrong Declined
The page as a PDF, image included 6 0 2
The page’s text layer only 0 1 7
Search tools over the whole document 2 5 1

The middle row is the honest failure. Handed flat text, the model mostly says it cannot tell, which costs you a retry.

The bottom row is the one that should worry you, because it is the pattern the first half of this post recommends. Give the same model search tools and it commits to a wrong number five times out of eight. Retrieval appears to raise its confidence that it has found the answer, so it answers. Three of those five were figures that exist nowhere in the text at all.


Finding the pages that are pictures

The fix is to look at the page. The question is which pages, because rendering all 171 would cost more than uploading the document.

pdf_read_pages has a detect_charts flag that sounds exactly right. On both chart pages of this filing it returns 0, and it is not broken: it looks for vector plot geometry, and these charts are flat JPEGs pasted into the page. There is no geometry to find.

The signal that does work is image_count, which the same call already returns: 2 on page 61, 6 on page 64, 0 on the pages that are only tables. Across the filing, 8 pages of 171 carry a picture. You pay for images on 5% of the document.

One of those eight is the cover logo, which comes back NO CHART and gets dropped, so seven pages reach the vision pass. Keep that check rather than hand-listing the pages: image_count finds anything drawn, and a letterhead is not a chart.


The whole-document code

import anthropic
from pdf_mcp.server import (pdf_read_all, pdf_read_pages,
                            pdf_render_pages)

PDF = "meta-10k-2022.pdf"     # 171 pages, too many for Haiku
TEXT_MODEL = "claude-haiku-4-5"   # 171 pages of prose
CHART_MODEL = "claude-opus-5"     # 7 pages of pictures
WINDOW = 25

client = anthropic.Anthropic()


def ask(system, blocks, model, max_tokens=2000):
    r = client.messages.create(
        model=model, max_tokens=max_tokens, system=system,
        messages=[{"role": "user", "content": blocks}])
    return "".join(b.text for b in r.content if b.type == "text")


def picture_pages(first, last):
    """detect_charts only finds vector charts, and these are
    rasterised, so key off image_count instead."""
    r = pdf_read_pages(path=PDF, pages=f"{first}-{last}")
    return [p["page"] for p in r["pages"] if p["image_count"]]


def read_charts(page):
    # one page, one look, on its own
    # pdf_render_pages returns [metadata, ImageContent]
    img = pdf_render_pages(path=PDF, pages=str(page), dpi=150)[1]
    return ask(
        "You read charts out of financial filings.",
        [{"type": "text", "text":
          f"Page {page}. For every chart give its title, then "
          "each series with its labels and every value you can "
          "read, as one table per chart."},
         {"type": "image", "source": {
             "type": "base64", "media_type": "image/png",
             "data": img.data}}],
        CHART_MODEL, max_tokens=6000)


total = pdf_read_all(path=PDF, max_pages=1)["total_pages"]
notes, charts, page = [], [], 1

while page <= total:
    last = min(page + WINDOW - 1, total)
    text = pdf_read_all(path=PDF, start_page=page,
                        max_pages=WINDOW)["full_text"]
    notes.append(ask(
        "You are reading a 10-K in sections.",
        [{"type": "text", "text":
          f"Take notes on pages {page}-{last} for a summary "
          f"of the whole filing.\n\n{text}"}], TEXT_MODEL))
    for p in picture_pages(page, last):
        out = read_charts(p)
        if "NO CHART" not in out[:120]:   # skips the cover logo
            charts.append(out)
    page = last + 1

print(ask(
    "You write briefings from section notes.",
    [{"type": "text", "text": "\n\n".join(notes + charts) +
      "\n\nStart with a table of worldwide revenue for all "
      "nine quarters shown in the charts (ad, non-ad, total, "
      "ARPP). Then summarise the business, the 2022 results "
      "and the segment split."}],
    TEXT_MODEL, max_tokens=3000))

One line matters more than it looks: read_charts is a separate call, with nothing in it but the instruction and one image. My first version attached the rendered pages to the same request as the window text, and the model noted a single chart, skipped the rest, and let not one chart value reach the notes. Seven images behind 76,000 characters of prose lose every time.


What it costs, and which model does what

I scored two of those seven pages, the revenue ones, by reading the renders myself and checking every figure: 81 of 81 values right, including all four regional series on page 64. The other five were transcribed but never checked, so treat 81 of 81 as a result about two pages rather than the whole document. The briefing opens with a table that appears nowhere in the document’s text:

Quarter Ad Non-ad Total ARPP
Q4 2021 32,639 1,032 33,671 $9.39
Q1 2022 26,998 910 27,908 $7.72
Q2 2022 28,152 670 28,822 $7.91
Q3 2022 27,237 477 27,714 $7.53
Q4 2022 31,254 911 32,165 $8.63
Pass Model Input Output Cost
Text and merge Haiku 4.5 150,580 11,192 $0.21
Charts, 7 pages Opus 5 22,368 8,569 $0.33
Total   172,948 19,761 $0.53

The split is the point. Asked to transcribe page 61, Haiku folded the bar totals into the advertising series, shifted the rest by two, and turned 670 into 870. Opus 5, same image and same prompt, got 27 of 27.

That is not simply “the big model is better”, because it is the same Haiku that answered targeted questions about that page without a single wrong number. Transcribing 27 values in one pass drifts where answering one question does not, so the weakness is in the shape of the task rather than the page.

So route by content type rather than by document. The cheap model reads 171 pages of prose, the expensive one looks at seven pictures and is 61% of a 53 cent bill. Uploading this document to answer a single question cost $2.29, and Haiku will not take it at any price.


Which branch you are on

Both paths end in the same place: the PDF itself never enters a request, so the page cap is never consulted. What differs is what you send instead.

If you have a question, send the pages that matched, and expect it to cost fractions of a cent. If you need the whole document, send every page’s text in windows and every picture as a picture, and expect about half a dollar for 171 pages. The expensive mistake is running the first branch on a whole-document task: search tools over a chart got five of eight answers wrong, confidently, which is worse than the $2.29 upload you were avoiding.


FAQ

All figures measured or checked on 6 September 2026 against Anthropic’s docs and a live API key. They move.

What is the maximum PDF size for the Anthropic API?

32MB for the whole Messages request, not just the PDF.

Amazon Bedrock caps at 20MB and Google Cloud at 30MB. The Files API takes uploads up to 500MB and a file_id keeps the payload small, but it raises neither the page cap nor the context window. Sending a PDF inline base64-encodes it, a 1.33x expansion, so the practical ceiling is around 24MB of actual file.

Why does the same PDF work on one Claude model and fail on another?

Because the page cap tracks the model’s context window: 100 pages below 1M tokens of context, 600 at 1M.

A 171-page file is fine on Claude Opus 5 and returns a 400 on Claude Haiku 4.5, unchanged. The error blames the document, which is the one thing that did not change, so a cost-motivated model swap can be slow to diagnose.

How many tokens does a PDF page use?

About 2,700 on a text-dense page, counting the extracted text and the rendered page image together.

Only about 1,123 of those are text, which is below the documented 1,500-3,000 band rather than inside it. The other 1,550 or so are the rendered image, charged whether or not the page has anything visual on it. Measure your own document with count_tokens, which is free.

How do I summarize a PDF that is too long to send?

Window the text off disk, take notes per window, then merge the notes.

Search-first does not apply, because a summary has no query to match a page against. Read the text layer in fixed page ranges instead, summarise each range, and combine. The one thing to watch is that the text layer keeps a chart’s numbers and drops the labels that say what they are, so render the pages that carry images and read those separately.

Can I send a 600-page PDF to the Claude API?

Only if it is unusually sparse.

At 2,700 tokens per page, 600 pages is about 1.6M tokens against a 1M context window, and on the 10-K tested the input passed 1M at 373 pages. count_tokens will not warn you either: it returned 1,608,628 for that build without complaint, because it answers what the input costs, not whether the request will run.


Or go straight to the code: jztan/pdf-mcp on GitHub · pip install pdf-mcp

ai-agents llm mcp rag
Kevin Tan

Kevin Tan

Cloud Solutions Architect and Engineering Leader based in Singapore. I write about AWS, distributed systems, and building reliable software at scale.