Every time you send a PDF to Claude through the Messages API, you base64-encode it and shove the whole thing into your request body. That works fine for one-off calls, but if you’re referencing the same document across multiple requests – or processing dozens of files in a pipeline – you’re wasting bandwidth and time re-uploading identical bytes.

The Files API fixes this. Upload a file once, get back a file_id, and reference that ID in any future Messages request. The file persists on Anthropic’s servers until you delete it. No more base64 encoding, no more bloated payloads.

The Files API is currently in beta, so all calls go through the client.beta namespace.

Upload a File

Install the SDK and set your API key:

1
2
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-..."

Upload a PDF:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import anthropic

client = anthropic.Anthropic()

# Upload a PDF document
uploaded_file = client.beta.files.upload(
    file=("quarterly-report.pdf", open("quarterly-report.pdf", "rb"), "application/pdf"),
)

print(f"File ID: {uploaded_file.id}")
print(f"Filename: {uploaded_file.filename}")
print(f"Size: {uploaded_file.size_bytes} bytes")

The response gives you a file object with an id field – that’s what you’ll use everywhere else. The upload supports PDFs (application/pdf), plain text (text/plain), and images (image/jpeg, image/png, image/gif, image/webp).

You can also upload from a pathlib.Path directly:

1
2
3
4
5
6
7
8
from pathlib import Path
import anthropic

client = anthropic.Anthropic()

uploaded_file = client.beta.files.upload(
    file=Path("quarterly-report.pdf"),
)

Reference Files in Messages

Once a file is uploaded, pass its file_id in a document content block instead of base64 data:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import anthropic

client = anthropic.Anthropic()

# Assume file_id from a previous upload
file_id = "file_011CNha8iCJcU1wXNR6q4V8w"

response = client.beta.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=2048,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "file",
                        "file_id": file_id,
                    },
                    "title": "Q4 2025 Quarterly Report",  # optional metadata
                },
                {
                    "type": "text",
                    "text": "Summarize the key financial metrics from this report.",
                },
            ],
        }
    ],
    betas=["files-api-2025-04-14"],
)

print(response.content[0].text)

The source.type is "file" (not "base64" like the standard PDF approach). You can optionally add title and context fields to give Claude extra metadata about the document. Since the API is in beta, you must pass betas=["files-api-2025-04-14"] to client.beta.messages.create.

For images, use an image block instead of document:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
response = client.beta.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "file",
                        "file_id": "file_011CPMxVD3fHLUhvTqtsQA5w",
                    },
                },
                {"type": "text", "text": "Describe what you see in this image."},
            ],
        }
    ],
    betas=["files-api-2025-04-14"],
)

Process Multiple Documents in One Request

You can stack multiple file references in a single message. This is perfect for comparison tasks, multi-document summaries, or cross-referencing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import anthropic

client = anthropic.Anthropic()

# Upload two reports
file_q3 = client.beta.files.upload(
    file=("q3-report.pdf", open("q3-report.pdf", "rb"), "application/pdf"),
)
file_q4 = client.beta.files.upload(
    file=("q4-report.pdf", open("q4-report.pdf", "rb"), "application/pdf"),
)

# Compare both documents in one request
response = client.beta.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=4096,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {"type": "file", "file_id": file_q3.id},
                    "title": "Q3 2025 Report",
                },
                {
                    "type": "document",
                    "source": {"type": "file", "file_id": file_q4.id},
                    "title": "Q4 2025 Report",
                },
                {
                    "type": "text",
                    "text": "Compare the revenue and profit margins between Q3 and Q4. Highlight significant changes.",
                },
            ],
        }
    ],
    betas=["files-api-2025-04-14"],
)

print(response.content[0].text)

The title field on each document block helps Claude distinguish between the files when generating its response. Always set it when sending multiple documents.

Extract Structured Data from Uploaded Documents

Combining the Files API with a structured extraction prompt gives you a clean pipeline for pulling data out of invoices, contracts, or any document with repeatable structure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import anthropic
import json

client = anthropic.Anthropic()

# Upload an invoice
invoice_file = client.beta.files.upload(
    file=("invoice-2025-001.pdf", open("invoice-2025-001.pdf", "rb"), "application/pdf"),
)

extraction_prompt = """Extract the following fields from this invoice as JSON:
- invoice_number
- date
- vendor_name
- line_items (array of {description, quantity, unit_price, total})
- subtotal
- tax
- total_due

Return ONLY valid JSON, no other text."""

response = client.beta.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=2048,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {"type": "file", "file_id": invoice_file.id},
                },
                {"type": "text", "text": extraction_prompt},
            ],
        }
    ],
    betas=["files-api-2025-04-14"],
)

invoice_data = json.loads(response.content[0].text)
print(json.dumps(invoice_data, indent=2))

Because the file is already uploaded, you can re-run extraction with different prompts without re-uploading. That’s the real win – iterate on your prompt without paying the upload cost each time.

Enable Citations on Uploaded Files

You can enable citations to get source references back to specific parts of the document:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import anthropic

client = anthropic.Anthropic()

file_id = "file_011CNha8iCJcU1wXNR6q4V8w"

response = client.beta.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=2048,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {"type": "file", "file_id": file_id},
                    "citations": {"enabled": True},
                },
                {
                    "type": "text",
                    "text": "What were the total revenue figures mentioned in this report?",
                },
            ],
        }
    ],
    betas=["files-api-2025-04-14"],
)

for block in response.content:
    print(block)

When citations are enabled, response content blocks will include references pointing back to the specific pages and text spans in the source document.

File Lifecycle Management

List All Files

1
2
3
4
5
6
7
import anthropic

client = anthropic.Anthropic()

files = client.beta.files.list()
for f in files.data:
    print(f"  {f.id} | {f.filename} | {f.size_bytes} bytes | {f.created_at}")

Get File Metadata

1
2
3
4
5
6
file_info = client.beta.files.retrieve_metadata("file_011CNha8iCJcU1wXNR6q4V8w")
print(f"Filename: {file_info.filename}")
print(f"MIME type: {file_info.mime_type}")
print(f"Size: {file_info.size_bytes}")
print(f"Created: {file_info.created_at}")
print(f"Downloadable: {file_info.downloadable}")

Delete a File

1
2
result = client.beta.files.delete("file_011CNha8iCJcU1wXNR6q4V8w")
print(result)  # Confirms deletion

Deleted files cannot be recovered. Any in-progress Messages API calls using the file will still work, but new requests referencing that file_id will fail.

Download Files

You can download files that were created by the code execution tool or skills (not files you uploaded yourself):

1
2
3
4
file_content = client.beta.files.download("file_011CNha8iCJcU1wXNR6q4V8w")

with open("output-chart.png", "wb") as f:
    f.write(file_content)

Storage Limits and Billing

A few things to keep in mind:

  • Max file size: 500 MB per file
  • Total storage: 100 GB per organization
  • File operations are free – uploading, listing, deleting cost nothing
  • Token billing applies when you reference files in Messages requests (charged as input tokens)
  • Rate limit: roughly 100 file API requests per minute during the beta
  • Files are scoped to your workspace – any API key in the same workspace can access them

Common Errors and Fixes

File not found (404) – The file_id doesn’t exist or belongs to a different workspace. Double-check the ID and make sure you’re using the same API key (or one in the same workspace) that uploaded the file.

Invalid file type (400) – You’re using the wrong content block type. PDFs and text files go in document blocks. Images go in image blocks. Mixing them up (putting a JPEG in a document block) triggers this error.

Exceeds context window size (400) – The file’s content is too large for the model’s context window. A 500 MB plain text file will blow past any model’s limit. Split large text files into chunks before uploading.

File too large (413) – File exceeds the 500 MB upload limit. Compress or split the file.

Storage limit exceeded (403) – Your organization hit the 100 GB cap. Delete old files with client.beta.files.delete() to free up space.

Missing beta header – If you call client.files.create() instead of client.beta.files.upload(), you’ll get an error. The Files API lives under the beta namespace. All file operations must go through client.beta.files.*, and messages referencing files must use client.beta.messages.create() with betas=["files-api-2025-04-14"].

Invalid filename (400) – Filenames must be 1-255 characters and can’t contain <, >, :, ", |, ?, *, \, /, or Unicode control characters (0-31). Rename the file before uploading.