File Upload API for Python: Django and Flask Examples

April 3, 2026 · 11 min read

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

curl -X POST https://filepost.dev/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

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:

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:

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 Key

Managing 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.