Python
Requests library
File upload
HTTP
Programming tutorial

How to upload file with python requests?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Python's requests library uploads files via multipart/form-data POST requests using the files parameter. You pass a dictionary mapping field names to file objects or tuples of (filename, file_object, content_type). The library handles the multipart encoding, Content-Type header, and boundary generation automatically. For large files, use streaming uploads to avoid loading the entire file into memory.

Basic File Upload

python
1import requests
2
3# Simple upload — open file in binary mode
4with open("report.pdf", "rb") as f:
5    response = requests.post(
6        "https://api.example.com/upload",
7        files={"file": f}
8    )
9
10print(response.status_code)  # 200
11print(response.json())       # {"id": "abc123", "filename": "report.pdf"}

The files parameter tells requests to use multipart/form-data encoding. The key "file" is the form field name expected by the server.

Specifying Filename and Content-Type

python
1import requests
2
3# Tuple format: (filename, file_object, content_type)
4with open("data.csv", "rb") as f:
5    response = requests.post(
6        "https://api.example.com/upload",
7        files={"file": ("monthly_report.csv", f, "text/csv")}
8    )
9
10# Upload with custom headers per file
11with open("image.png", "rb") as f:
12    response = requests.post(
13        "https://api.example.com/upload",
14        files={"file": ("photo.png", f, "image/png", {"X-Custom": "value"})}
15    )

The tuple form lets you override the filename sent to the server (useful when the local filename differs from the desired upload name).

Uploading Multiple Files

python
1import requests
2
3# Multiple files to the same field name
4files = [
5    ("files", ("doc1.pdf", open("doc1.pdf", "rb"), "application/pdf")),
6    ("files", ("doc2.pdf", open("doc2.pdf", "rb"), "application/pdf")),
7    ("files", ("image.jpg", open("image.jpg", "rb"), "image/jpeg")),
8]
9
10response = requests.post("https://api.example.com/upload", files=files)
11
12# Multiple files to different field names
13files = {
14    "avatar": ("avatar.png", open("avatar.png", "rb"), "image/png"),
15    "resume": ("resume.pdf", open("resume.pdf", "rb"), "application/pdf"),
16}
17
18response = requests.post("https://api.example.com/profile", files=files)

Upload with Additional Form Data

python
1import requests
2
3with open("report.pdf", "rb") as f:
4    response = requests.post(
5        "https://api.example.com/upload",
6        files={"file": ("report.pdf", f, "application/pdf")},
7        data={
8            "title": "Monthly Report",
9            "category": "finance",
10            "public": "true",
11        }
12    )

The data parameter sends additional form fields alongside the file. Both are encoded in the same multipart/form-data request.

Upload with Authentication

python
1import requests
2
3with open("backup.zip", "rb") as f:
4    # Bearer token
5    response = requests.post(
6        "https://api.example.com/upload",
7        files={"file": f},
8        headers={"Authorization": "Bearer YOUR_TOKEN_HERE"},
9    )
10
11    # Basic auth
12    response = requests.post(
13        "https://api.example.com/upload",
14        files={"file": f},
15        auth=("username", "password"),
16    )

Uploading In-Memory Data (No File on Disk)

python
1import requests
2import io
3import json
4
5# Upload a string as a file
6csv_data = "name,age\nAlice,30\nBob,25"
7response = requests.post(
8    "https://api.example.com/upload",
9    files={"file": ("data.csv", io.BytesIO(csv_data.encode()), "text/csv")}
10)
11
12# Upload JSON as a file
13data = {"users": [{"name": "Alice"}, {"name": "Bob"}]}
14json_bytes = json.dumps(data, indent=2).encode()
15response = requests.post(
16    "https://api.example.com/upload",
17    files={"file": ("data.json", io.BytesIO(json_bytes), "application/json")}
18)

Streaming Upload (Large Files)

python
1import requests
2
3# requests-toolbelt for streaming multipart uploads
4from requests_toolbelt import MultipartEncoder
5
6encoder = MultipartEncoder(fields={
7    "file": ("large_video.mp4", open("large_video.mp4", "rb"), "video/mp4"),
8    "title": "My Video",
9})
10
11response = requests.post(
12    "https://api.example.com/upload",
13    data=encoder,
14    headers={"Content-Type": encoder.content_type},
15)
16
17# With progress monitoring
18from requests_toolbelt import MultipartEncoderMonitor
19
20def progress_callback(monitor):
21    pct = monitor.bytes_read / monitor.len * 100
22    print(f"\rUploading: {pct:.1f}%", end="", flush=True)
23
24encoder = MultipartEncoder(fields={
25    "file": ("large_file.zip", open("large_file.zip", "rb"), "application/zip"),
26})
27monitor = MultipartEncoderMonitor(encoder, progress_callback)
28
29response = requests.post(
30    "https://api.example.com/upload",
31    data=monitor,
32    headers={"Content-Type": monitor.content_type},
33)

Error Handling

python
1import requests
2
3try:
4    with open("file.pdf", "rb") as f:
5        response = requests.post(
6            "https://api.example.com/upload",
7            files={"file": f},
8            timeout=60,  # 60 second timeout
9        )
10        response.raise_for_status()  # Raises HTTPError for 4xx/5xx
11        print("Upload successful:", response.json())
12
13except FileNotFoundError:
14    print("File not found")
15except requests.exceptions.Timeout:
16    print("Upload timed out")
17except requests.exceptions.HTTPError as e:
18    print(f"Server error: {e.response.status_code} - {e.response.text}")
19except requests.exceptions.ConnectionError:
20    print("Could not connect to server")

Common Pitfalls

  • Opening files in text mode instead of binary: open("file.pdf", "r") opens in text mode, which corrupts binary files during upload. Always use open("file.pdf", "rb") with the "rb" flag for file uploads.
  • Setting Content-Type header manually: When using the files parameter, requests automatically sets the Content-Type to multipart/form-data with the correct boundary. Manually setting headers={"Content-Type": "multipart/form-data"} omits the boundary and breaks the upload.
  • Not closing file handles: Opening files without with statements or close() calls leaks file descriptors. When uploading multiple files in a list, ensure you close all handles after the request completes, or use context managers.
  • Loading large files entirely into memory: requests.post(files={"file": open("10gb.zip", "rb")}) reads the entire file into memory for encoding. For files over 100MB, use requests-toolbelt's MultipartEncoder for streaming uploads.
  • Using data instead of files for file uploads: Passing file content via the data parameter sends it as application/x-www-form-urlencoded, not multipart/form-data. Most servers expect multipart encoding for file uploads. Always use the files parameter.

Summary

  • Use requests.post(url, files={"field": file_object}) for simple file uploads
  • Pass a tuple (filename, file_object, content_type) to control the filename and MIME type
  • Combine files and data parameters to send form fields alongside the file
  • Use requests-toolbelt.MultipartEncoder for streaming large files without loading them into memory
  • Always open files in binary mode ("rb") and use with statements for proper cleanup
  • Do not manually set the Content-Type header — requests handles the multipart boundary automatically

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.