FastAPI File Upload
Upload Files from FastAPI Async Routes with httpx
Upload files from FastAPI async routes with httpx. No SDK, REST API, CDN delivery, prepaid credits.
FastAPI is async to its core, so the right way to upload a file is with an async HTTP client — httpx.AsyncClient — not by wrapping a synchronous boto3 call in a thread pool. files.link is three awaited calls that fit cleanly into FastAPI's event loop: POST metadata, PUT bytes, POST confirm. No boto3, no aiobotocore, no AWS credentials chain, no bucket.
FastAPI gives you the incoming file as an UploadFile, a SpooledTemporaryFile-backed object with async read methods, so you can move data without blocking the loop:
import os, httpx
from fastapi import FastAPI, UploadFile
app = FastAPI()
KEY = os.environ['FILESLINK_KEY']
BASE = 'https://api.files.link'
FOLDER = os.environ['FILESLINK_FOLDER']
@app.post('/upload')
async def upload(file: UploadFile):
data = await file.read()
async with httpx.AsyncClient() as client:
r1 = await client.post(f'{BASE}/v1/files/{FOLDER}',
headers={'Authorization': KEY},
json={'filesMetadata': [{'name': file.filename, 'size': len(data), 'type': file.content_type}]})
slot = r1.json()['urls'][0]
await client.put(slot['url'], content=data, headers={'Content-Type': file.content_type})
await client.post(f'{BASE}/v1/files/confirm-upload',
headers={'Authorization': KEY}, json={'ids': [slot['id']]})
return {'id': slot['id']}Reuse a single httpx.AsyncClient across requests (create it in a lifespan handler and store it on app.state) so connections are pooled instead of re-dialed on every upload. For uploads where you do not want the client waiting on the transfer, hand the three-call flow to BackgroundTasks so the response returns the moment metadata is accepted; for retry guarantees, push it to a task queue like arq or Celery instead.
Note that file.read() with no argument loads the whole file into memory — for large media, read in chunks with await file.read(chunk_size) and use the multipart endpoint, PUTting each part to its own presigned URL as you stream.
The Authorization header takes the raw API key (no Bearer prefix), sourced from pydantic-settings BaseSettings reading your .env and injected via Depends. files.link returns CDN-backed URLs with per-file visibility, storage encrypted at rest, and prepaid-credit billing — async upload without the boto3 footprint or a surprise egress bill.
Benefits With No Complexity
Global CDN delivery
Edge-cached worldwide
Signed-URL security
What You Get
Unlimited files
Unlimited storage
Public + Private storage
CDN ready links
Prepaid credits
More coming soon
How files.link Works
1. Upload
2. Copy
3. Use Anywhere
Why Developers Choose files.link
A Better Way to Store & Deliver Files
| AWS S3 (boto3 / aiobotocore) | Cloudinary (python-cloudinary) | files.link | |
|---|---|---|---|
| Async-native with httpx (no thread-pool wrap) | |||
| No boto3 / AWS credentials chain | |||
| Reusable pooled AsyncClient | |||
| Global CDN delivery on every file | |||
| Prepaid credits (no surprise bills) |
Calculate Your Needs
Storage
Egress
CDN Bandwidth
Wire Up FastAPI File Uploads
- Global CDN delivery
- Edge-cached worldwide
- Signed-URL security
- Unlimited files
- Unlimited storage
- Public + Private storage
- No subscription — prepaid credits keep spend predictable