Upload Files

Su Suriya Ravichandran Updated 10 Nov 2026
6 min read ·Lesson 115 of 200

The Files API

Sending the same large file in every request is wasteful. The bytes travel again each time, the request grows, and the cost of the file's data repeats with every call. The Files API solves this: you upload a file once, receive a file ID, and reference that ID in later messages.

upload once ──► file_id ──► message 1 ──► message 2 ──► message 3 ...

The Files API works with images, PDFs, and other supported files. Lesson 115 covers uploading and managing files. Lesson 116 covers reusing file IDs in practice.


Uploading a File

The Python SDK uploads a file with client.files.upload(), passing a tuple of file name, file object, and media type:

from anthropic import Anthropic

client = Anthropic()

with open("report.pdf", "rb") as handle:
    uploaded = client.files.upload(file=("report.pdf", handle, "application/pdf"))

print(uploaded.id)     # The file ID to use in messages
  • The tuple supplies the file name, the open file, and the media type. The media type tells the API how to interpret the bytes, so it must be correct.
  • uploaded.id is the reference you store and reuse.

Then reference the file in a message, using the file source type:

{"type": "document", "source": {"type": "file", "file_id": uploaded.id}}     # For a PDF
{"type": "image", "source": {"type": "file", "file_id": uploaded.id}}        # For an image

Version note: some older tutorials and documentation show the same operation as client.beta.files.upload(...) with a betas=["files-api-2025-04-14"] header. The current vision and PDF documentation shows client.files.upload(...) without that header. Check the current documentation for your SDK version before publishing code, and use the form it recommends.


Managing Uploaded Files

Uploaded files stay in your workspace until you delete them. They can be listed, inspected, and removed. The documentation describes these operations as HTTP endpoints:

OperationEndpointPurpose
UploadPOST /v1/filesStore a file, and receive its ID
ListGET /v1/filesSee the files in your workspace
Get metadataGET /v1/files/{file_id}Check a file's details
DeleteDELETE /v1/files/{file_id}Remove a file you no longer need

In the Python SDK, these operations are methods on client.files. Confirm the exact method names in the reference for your installed version. Deleting by ID looks like this:

client.files.delete(uploaded.id)

Delete files you no longer need. Uploaded files can contain personal or confidential data, so they should be treated like any other stored data (Lesson 171).


A Safe Upload Function

Before uploading, apply the same checks as before sending data directly: verify the bytes, enforce a size policy, and choose the media type from the content:

from media_utils import detect_media_type

MAX_UPLOAD_BYTES = 20 * 1024 * 1024    # Our own policy; check the current documented limits too


def media_type_for(data: bytes) -> str | None:
    if data.startswith(b"%PDF-"):
        return "application/pdf"
    return detect_media_type(data)


def upload_checked(client, name: str, data: bytes):
    media_type = media_type_for(data)
    if media_type is None:
        raise ValueError(f"{name}: unsupported file type")
    if len(data) > MAX_UPLOAD_BYTES:
        raise ValueError(f"{name}: {len(data):,} bytes exceeds the local limit of {MAX_UPLOAD_BYTES:,}")
    return client.files.upload(file=(name, data, media_type))

Passing the bytes directly, instead of an open file, works for data already in memory, such as an upload received by a web application.


Practical Example: Upload Once, Ask Twice

This example uploads a PDF, asks two questions that reuse the same file ID, and reports the file ID used by each request.

"""upload_once.py: upload a PDF once and use its file ID for two questions."""

import sys
from pathlib import Path

from anthropic import Anthropic

client = Anthropic()
MAX_UPLOAD_BYTES = 20 * 1024 * 1024


def upload_checked(name: str, data: bytes):
    if not data.startswith(b"%PDF-"):
        raise ValueError(f"{name}: not a PDF")
    if len(data) > MAX_UPLOAD_BYTES:
        raise ValueError(f"{name}: exceeds the local limit of {MAX_UPLOAD_BYTES:,} bytes")
    return client.files.upload(file=(name, data, "application/pdf"))


def ask(file_id: str, question: str) -> str:
    message = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": [
            {"type": "document", "source": {"type": "file", "file_id": file_id}},
            {"type": "text", "text": question},
        ]}],
    )
    return "".join(block.text for block in message.content if block.type == "text").strip()


if __name__ == "__main__":
    path = Path(sys.argv[1])
    uploaded = upload_checked(path.name, path.read_bytes())
    print(f"uploaded {path.name} as {uploaded.id}")

    print("Q1:", ask(uploaded.id, "What is the document's title?"))
    print("Q2:", ask(uploaded.id, "List the main section headings."))

    client.files.delete(uploaded.id)
    print("deleted", uploaded.id)

Example run (answers depend on the document):

$ python upload_once.py report.pdf
uploaded report.pdf as file_01ABC...
Q1: Annual Report 2026
Q2: Overview; Financial Summary; Risks; Outlook
deleted file_01ABC...

How It Works

  1. upload_checked() verifies the content and the local size policy before uploading, so invalid files never reach the Files API.
  2. The media type is fixed to application/pdf because the signature check has already confirmed the content.
  3. client.files.upload() stores the file and returns an object whose id identifies it.
  4. Both questions use the same ID, so the file is sent by reference rather than as bytes each time.
  5. client.files.delete() removes the file at the end. In a real application, deletion would follow your retention policy, not happen after every question.

Common Mistakes

Uploading Without Checking the Content

What happens: unsupported or corrupt files are stored and then fail when used.

Fix: check the signature and size before uploading.

Wrong Media Type in the Upload

What happens: the file is stored with a type that does not match its content, and later requests fail.

Fix: derive the media type from the bytes.

Uploading the Same File Repeatedly

What happens: storage grows, and every upload costs time and bandwidth.

Fix: store the file ID, and reuse it (Lesson 116).

Never Deleting Files

What happens: old confidential documents accumulate in your workspace.

Fix: delete files on a schedule, or when a task completes.

Mixing Up the Beta and Current Methods

What happens: code copied from older tutorials fails, or sends unnecessary headers.

Fix: check the current documentation for your SDK version.


Best Practices

  • Validate content before uploading.
  • Derive the media type from the bytes.
  • Keep file IDs in your own database, with the file's origin and owner.
  • Delete files according to a retention policy.
  • Treat uploads as sensitive data.
  • Check the current API reference for upload and management methods.

Exercise

  1. Upload the same PDF twice. Are the two file IDs the same? What does this tell you about the Files API's behavior?
  2. Add a function that uploads an image using detect_media_type() for the media type, and then references it in a message.
  3. Write a function that deletes every file ID in a list, and returns the number deleted, continuing even if one deletion fails.
  4. Explain why the upload helper accepts bytes instead of a file path.
Show answers for steps 3 and 4

Step 3:

def delete_all(client, file_ids: list[str]) -> int:
    deleted = 0
    for file_id in file_ids:
        try:
            client.files.delete(file_id)
            deleted += 1
        except anthropic.APIError as error:
            print(f"could not delete {file_id}: {type(error).__name__}")
    return deleted

Catching errors per file means one failure does not leave the rest behind. Import anthropic to use anthropic.APIError.

Step 4: bytes work for every source. A file read from disk, received from a web upload, or generated in memory can all be passed in the same way. Accepting a path would tie the helper to the file system, and would make web uploads, which arrive as bytes, awkward to handle.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Upload Files and get answers drawn from it.

Signed-in readers only.