File Upload API for Python: Django and Flask Examples
Python is one of the most popular languages for building web applications, APIs, and automation scripts. When your Python project needs to handle file uploads, you have two options: build your own storage infrastructure with S3 buckets, CDN configuration, and disk management, or use a file hosting API that handles all of that for you.
This guide shows you how to upload files from Python to FilePost using the requests library, Django views, Flask routes, and async httpx. Every example includes proper error handling and is ready to paste into a real project.
Prerequisites
- Python 3.8+
- A FilePost API key. The free tier gives you 300 uploads per month with no credit card. Sign up with a single request:
curl -X POST https://filepost.dev/v1/signup \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
- The requests library (install with
pip install requests) for the basic examples, or httpx (pip install httpx) for the async examples.
Basic Upload with requests
The simplest way to upload a file from Python is with the requests library. The files parameter handles multipart encoding automatically.
import requests
API_KEY = "your_api_key_here"
def upload_file(file_path):
with open(file_path, "rb") as f:
response = requests.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": API_KEY},
files={"file": f},
timeout=30,
)
response.raise_for_status()
data = response.json()
return data["url"]
# Usage
url = upload_file("report.pdf")
print(f"Hosted at: {url}")
A few important details:
- Always open files in binary mode (
"rb"). Text mode will corrupt binary files like images and PDFs. - Do not set the
Content-Typeheader manually. Therequestslibrary generates the correctmultipart/form-databoundary automatically when you use thefilesparameter. Setting it yourself will break the request. - The
timeout=30prevents the request from hanging indefinitely on slow connections. Increase this for larger files. - The
withstatement ensures the file handle is closed after upload, even if an exception occurs.
Upload with Full Error Handling
A production-ready upload function should handle every common failure case:
import requests
from pathlib import Path
API_KEY = "your_api_key_here"
UPLOAD_URL = "https://filepost.dev/v1/upload"
class UploadError(Exception):
"""Raised when a file upload fails."""
def __init__(self, message, status_code=None):
super().__init__(message)
self.status_code = status_code
def upload_file(file_path, timeout=30):
"""Upload a file to FilePost and return the CDN URL.
Args:
file_path: Path to the file on disk.
timeout: Request timeout in seconds.
Returns:
dict with keys: url, file_id, size
Raises:
UploadError: If the upload fails for any reason.
"""
path = Path(file_path)
if not path.exists():
raise UploadError(f"File not found: {file_path}")
if not path.is_file():
raise UploadError(f"Not a file: {file_path}")
try:
with open(path, "rb") as f:
response = requests.post(
UPLOAD_URL,
headers={"X-API-Key": API_KEY},
files={"file": (path.name, f)},
timeout=timeout,
)
except requests.exceptions.Timeout:
raise UploadError("Upload timed out. Try increasing the timeout for large files.")
except requests.exceptions.ConnectionError:
raise UploadError("Could not connect to FilePost. Check your internet connection.")
if response.status_code == 200:
return response.json()
elif response.status_code == 401:
raise UploadError("Invalid API key.", status_code=401)
elif response.status_code == 413:
raise UploadError("File exceeds the maximum size for your plan.", status_code=413)
elif response.status_code == 429:
raise UploadError("Monthly upload limit reached. Upgrade at https://filepost.dev/#pricing", status_code=429)
else:
raise UploadError(f"Upload failed with status {response.status_code}: {response.text}", status_code=response.status_code)
# Usage
try:
result = upload_file("invoice.pdf")
print(f"URL: {result['url']}")
print(f"File ID: {result['file_id']}")
print(f"Size: {result['size']} bytes")
except UploadError as e:
print(f"Upload error: {e}")
This version validates the file exists before attempting the upload, provides specific error messages for each HTTP status code, and wraps network exceptions in a clean custom error type.
Uploading In-Memory Files
Sometimes the file you need to upload does not exist on disk. Maybe you generated a PDF, downloaded data from another API, or created a CSV from a database query. You can upload raw bytes directly:
import requests
from io import BytesIO
def upload_bytes(data, filename, content_type="application/octet-stream"):
"""Upload raw bytes to FilePost.
Args:
data: bytes or BytesIO object
filename: Name for the uploaded file
content_type: MIME type of the file
Returns:
dict with keys: url, file_id, size
"""
if isinstance(data, bytes):
data = BytesIO(data)
response = requests.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": API_KEY},
files={"file": (filename, data, content_type)},
timeout=30,
)
response.raise_for_status()
return response.json()
# Example: upload a CSV generated in memory
import csv
import io
buffer = io.StringIO()
writer = csv.writer(buffer)
writer.writerow(["name", "email", "plan"])
writer.writerow(["Alice", "alice@example.com", "Pro"])
writer.writerow(["Bob", "bob@example.com", "Starter"])
csv_bytes = buffer.getvalue().encode("utf-8")
result = upload_bytes(csv_bytes, "users.csv", "text/csv")
print(f"CSV hosted at: {result['url']}")
The tuple format (filename, file_object, content_type) gives you full control over the upload metadata. The content_type parameter sets the MIME type that browsers will use when serving the file.
Django View: Accept Upload and Forward to FilePost
In a Django application, you typically receive a file upload from a form or AJAX request, then forward it to your hosting API. Here is a complete Django view that handles this:
# views.py
import requests
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.conf import settings
@csrf_exempt
@require_POST
def upload_file(request):
"""Accept a file upload from the client and forward it to FilePost."""
uploaded_file = request.FILES.get("file")
if not uploaded_file:
return JsonResponse({"error": "No file provided"}, status=400)
# Optional: validate file size before forwarding
max_size = 200 * 1024 * 1024 # 200MB for Starter plan
if uploaded_file.size > max_size:
return JsonResponse({"error": "File exceeds 200MB limit"}, status=413)
try:
response = requests.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": settings.FILEPOST_API_KEY},
files={"file": (uploaded_file.name, uploaded_file.read(), uploaded_file.content_type)},
timeout=60,
)
except requests.exceptions.RequestException as e:
return JsonResponse({"error": "Upload service unavailable"}, status=502)
if response.status_code == 200:
return JsonResponse(response.json())
else:
return JsonResponse(
{"error": f"Upload failed: {response.text}"},
status=response.status_code,
)
Add the URL pattern:
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("api/upload/", views.upload_file, name="upload_file"),
]
And store the API key in your Django settings:
# settings.py
import os
FILEPOST_API_KEY = os.environ.get("FILEPOST_API_KEY")
Django with a model field
If you want to store the FilePost URL in a Django model (for example, attaching files to a document record):
# models.py
from django.db import models
class Document(models.Model):
title = models.CharField(max_length=255)
file_url = models.URLField(blank=True)
file_id = models.CharField(max_length=64, blank=True)
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# views.py (updated to save to model)
@csrf_exempt
@require_POST
def upload_document(request):
title = request.POST.get("title", "Untitled")
uploaded_file = request.FILES.get("file")
if not uploaded_file:
return JsonResponse({"error": "No file provided"}, status=400)
response = requests.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": settings.FILEPOST_API_KEY},
files={"file": (uploaded_file.name, uploaded_file.read(), uploaded_file.content_type)},
timeout=60,
)
if response.status_code != 200:
return JsonResponse({"error": "Upload failed"}, status=502)
data = response.json()
doc = Document.objects.create(
title=title,
file_url=data["url"],
file_id=data["file_id"],
)
return JsonResponse({
"id": doc.id,
"title": doc.title,
"file_url": doc.file_url,
"file_id": doc.file_id,
})
Flask Route: Accept Upload and Forward to FilePost
Flask handles file uploads through request.files. Here is a complete route:
# app.py
import os
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
FILEPOST_API_KEY = os.environ.get("FILEPOST_API_KEY")
@app.route("/api/upload", methods=["POST"])
def upload_file():
"""Accept a file upload and forward it to FilePost."""
if "file" not in request.files:
return jsonify({"error": "No file provided"}), 400
uploaded_file = request.files["file"]
if uploaded_file.filename == "":
return jsonify({"error": "No file selected"}), 400
# Optional: validate file size
uploaded_file.seek(0, 2) # Seek to end
size = uploaded_file.tell()
uploaded_file.seek(0) # Seek back to start
max_size = 200 * 1024 * 1024 # 200MB
if size > max_size:
return jsonify({"error": "File exceeds 200MB limit"}), 413
try:
response = requests.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": FILEPOST_API_KEY},
files={"file": (uploaded_file.filename, uploaded_file.stream, uploaded_file.content_type)},
timeout=60,
)
except requests.exceptions.RequestException:
return jsonify({"error": "Upload service unavailable"}), 502
if response.status_code == 200:
return jsonify(response.json())
else:
return jsonify({"error": "Upload failed"}), response.status_code
if __name__ == "__main__":
app.run(debug=True)
Key Flask-specific details:
request.files["file"]returns aFileStorageobject. Its.streamproperty gives you the file data without reading it all into memory.- The file size check uses
seek()to find the file length without reading the entire file into a buffer. - Flask automatically handles multipart form parsing from incoming requests.
Flask with Blueprint
For larger applications, use a Blueprint to organize your upload routes:
# blueprints/uploads.py
import os
import requests
from flask import Blueprint, request, jsonify
uploads_bp = Blueprint("uploads", __name__)
FILEPOST_API_KEY = os.environ.get("FILEPOST_API_KEY")
@uploads_bp.route("/api/upload", methods=["POST"])
def upload():
if "file" not in request.files:
return jsonify({"error": "No file provided"}), 400
f = request.files["file"]
response = requests.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": FILEPOST_API_KEY},
files={"file": (f.filename, f.stream, f.content_type)},
timeout=60,
)
if response.status_code == 200:
return jsonify(response.json())
return jsonify({"error": "Upload failed"}), response.status_code
# In app.py:
# from blueprints.uploads import uploads_bp
# app.register_blueprint(uploads_bp)
Async Upload with httpx
If your application uses async Python (FastAPI, async Django, or any asyncio-based framework), use httpx instead of requests for non-blocking uploads:
import httpx
API_KEY = "your_api_key_here"
async def upload_file_async(file_path):
"""Upload a file asynchronously using httpx."""
async with httpx.AsyncClient(timeout=60) as client:
with open(file_path, "rb") as f:
response = await client.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": API_KEY},
files={"file": f},
)
response.raise_for_status()
return response.json()
# Usage with asyncio
import asyncio
async def main():
result = await upload_file_async("photo.jpg")
print(f"URL: {result['url']}")
asyncio.run(main())
Batch uploads with asyncio
One of the biggest advantages of async uploads is the ability to upload multiple files concurrently:
import httpx
import asyncio
from pathlib import Path
API_KEY = "your_api_key_here"
async def upload_one(client, file_path):
"""Upload a single file and return the result."""
with open(file_path, "rb") as f:
response = await client.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": API_KEY},
files={"file": (Path(file_path).name, f)},
)
if response.status_code == 200:
return {"file": file_path, "result": response.json()}
return {"file": file_path, "error": response.text}
async def upload_batch(file_paths, max_concurrent=5):
"""Upload multiple files with concurrency control."""
semaphore = asyncio.Semaphore(max_concurrent)
async def limited_upload(client, path):
async with semaphore:
return await upload_one(client, path)
async with httpx.AsyncClient(timeout=60) as client:
tasks = [limited_upload(client, path) for path in file_paths]
return await asyncio.gather(*tasks)
# Usage
files = ["doc1.pdf", "doc2.pdf", "image1.png", "image2.jpg"]
results = asyncio.run(upload_batch(files))
for r in results:
if "result" in r:
print(f"{r['file']}: {r['result']['url']}")
else:
print(f"{r['file']}: FAILED - {r['error']}")
The asyncio.Semaphore limits concurrent uploads to 5 at a time, preventing the client from opening too many connections simultaneously. Adjust the max_concurrent value based on your network capacity.
FastAPI Example
FastAPI is a natural fit for async file uploads. Here is a complete endpoint:
import os
import httpx
from fastapi import FastAPI, UploadFile, HTTPException
app = FastAPI()
FILEPOST_API_KEY = os.environ.get("FILEPOST_API_KEY")
@app.post("/api/upload")
async def upload_file(file: UploadFile):
if not file.filename:
raise HTTPException(status_code=400, detail="No file provided")
contents = await file.read()
async with httpx.AsyncClient(timeout=60) as client:
response = await client.post(
"https://filepost.dev/v1/upload",
headers={"X-API-Key": FILEPOST_API_KEY},
files={"file": (file.filename, contents, file.content_type)},
)
if response.status_code != 200:
raise HTTPException(status_code=502, detail="Upload to FilePost failed")
return response.json()
FastAPI's UploadFile type gives you automatic request parsing, OpenAPI documentation, and async file reading out of the box.
Start Uploading from Python in 30 Seconds
FilePost gives you 300 free uploads per month. No SDK to install, just use the requests library you already have. One endpoint, instant CDN URLs.
Get Your Free API KeyManaging Uploaded Files
After uploading files, you may need to list or delete them. Here are the management endpoints in Python:
import requests
API_KEY = "your_api_key_here"
BASE_URL = "https://filepost.dev/v1"
# List all uploaded files
def list_files():
response = requests.get(
f"{BASE_URL}/files",
headers={"X-API-Key": API_KEY},
)
return response.json()
# Delete a file by ID
def delete_file(file_id):
response = requests.delete(
f"{BASE_URL}/files/{file_id}",
headers={"X-API-Key": API_KEY},
)
return response.status_code == 200
# Usage
files = list_files()
for f in files:
print(f"ID: {f['file_id']}, URL: {f['url']}, Size: {f['size']}")
# Delete the first file
if files:
deleted = delete_file(files[0]["file_id"])
print(f"Deleted: {deleted}")
Comparison: FilePost vs Building Your Own
Before choosing a file hosting API, it is worth considering what the DIY alternative looks like:
| Task | DIY (S3 + CloudFront) | FilePost |
|---|---|---|
| Initial setup | Create bucket, configure CORS, set up IAM, add CloudFront distribution | One API call to sign up |
| Upload code | Install boto3, configure credentials, write upload function | requests.post() with API key header |
| CDN URLs | Configure CloudFront distribution, set up SSL certificate | Included in upload response |
| Cost model | Per-GB storage + per-GB egress + per-request fees | Flat monthly fee, unlimited storage and bandwidth |
| Maintenance | Monitor disk usage, rotate credentials, update SDKs | None |
For prototypes, MVPs, and small-to-medium applications, a hosted file upload API saves days of infrastructure work. For applications processing millions of files per month with complex access patterns, a self-managed solution may make more sense.
Next Steps
You now have working upload code for every major Python framework: plain requests, Django, Flask, FastAPI, and async httpx. Pick the example that matches your stack and adapt it to your application.
For the full API reference, check the FilePost API documentation. For the complete REST API file upload guide including multipart/form-data anatomy, error codes, Java, and PHP examples, see the dedicated technical reference.