Upload Files
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.idis 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 abetas=["files-api-2025-04-14"]header. The current vision and PDF documentation showsclient.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:
| Operation | Endpoint | Purpose |
|---|---|---|
| Upload | POST /v1/files | Store a file, and receive its ID |
| List | GET /v1/files | See the files in your workspace |
| Get metadata | GET /v1/files/{file_id} | Check a file's details |
| Delete | DELETE /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
upload_checked()verifies the content and the local size policy before uploading, so invalid files never reach the Files API.- The media type is fixed to
application/pdfbecause the signature check has already confirmed the content. client.files.upload()stores the file and returns an object whoseididentifies it.- Both questions use the same ID, so the file is sent by reference rather than as bytes each time.
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
- Upload the same PDF twice. Are the two file IDs the same? What does this tell you about the Files API's behavior?
- Add a function that uploads an image using
detect_media_type()for the media type, and then references it in a message. - Write a function that deletes every file ID in a list, and returns the number deleted, continuing even if one deletion fails.
- Explain why the upload helper accepts bytes instead of a file path.
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.