Response Headers

Difficulty: Intermediate

HTTP response headers provide metadata about the response itself - information about caching, content type, pagination, rate limiting, and more. FastAPI provides multiple ways to set custom response headers, from the simple Response parameter injection to returning full Response objects.

The simplest way to add custom headers is to declare a Response parameter in your handler. FastAPI injects a Response object that you can modify. Any headers you set on this object are included in the final response. You can still return your data normally, and FastAPI handles the serialization.

For full control over the response, return a Response object directly (like JSONResponse, PlainTextResponse, etc.). This gives you control over the body, status code, headers, and content type. However, you lose automatic serialization and response_model filtering.

Common custom headers include X-Request-Id for request tracing, X-Total-Count for pagination totals, Cache-Control for caching directives, X-RateLimit-Remaining for rate limit information, and Content-Disposition for file downloads.

Headers for pagination are particularly important for list endpoints. Instead of including pagination metadata in the response body, some APIs use headers like X-Total-Count, X-Page, X-Per-Page, and Link (for next/previous page URLs). This keeps the response body clean - just the array of items.

Note that custom headers should use the X- prefix by convention for application-specific headers, though this convention is technically deprecated. Standard headers like Cache-Control, Content-Type, and Content-Disposition have defined meanings and should be used according to their specifications.

Code examples

Adding Custom Headers via Response

from fastapi import FastAPI, Response
import uuid

app = FastAPI()

@app.get("/items/{item_id}")
def get_item(item_id: int, response: Response):
    response.headers["X-Request-Id"] = str(uuid.uuid4())
    response.headers["Cache-Control"] = "max-age=300, public"
    response.headers["X-Item-Version"] = "2"
    return {"id": item_id, "name": "Widget"}

# Response headers:
# X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
# Cache-Control: max-age=300, public
# X-Item-Version: 2
# Content-Type: application/json

The Response parameter is injected by FastAPI. Setting headers on it adds them to the final response. You can still return data normally - FastAPI handles JSON serialization and adds your custom headers.

Pagination Headers

from fastapi import FastAPI, Response, Query

app = FastAPI()

all_items = [{"id": i, "name": f"Item {i}"} for i in range(1, 101)]

@app.get("/items")
def list_items(
    page: int = Query(default=1, ge=1),
    per_page: int = Query(default=20, ge=1, le=100),
    response: Response = None,
):
    total = len(all_items)
    start = (page - 1) * per_page
    end = start + per_page
    items = all_items[start:end]

    response.headers["X-Total-Count"] = str(total)
    response.headers["X-Page"] = str(page)
    response.headers["X-Per-Page"] = str(per_page)
    response.headers["X-Total-Pages"] = str(-(-total // per_page))

    return items

# GET /items?page=2&per_page=10
# Response headers:
# X-Total-Count: 100
# X-Page: 2
# X-Per-Page: 10
# X-Total-Pages: 10
# Body: [{"id": 11, ...}, ..., {"id": 20, ...}]

Pagination metadata is sent via headers, keeping the response body as a clean array. X-Total-Count tells the client how many items exist in total. The client can calculate if there are more pages.

Direct Response Objects

from fastapi import FastAPI
from fastapi.responses import JSONResponse, StreamingResponse
import json

app = FastAPI()

@app.get("/download")
def download_report():
    data = {"report": "Q1 Sales", "total": 150000}
    content = json.dumps(data, indent=2)
    return JSONResponse(
        content=data,
        headers={
            "Content-Disposition": "attachment; filename=report.json",
            "X-Report-Generated": "2026-03-12",
        },
    )

@app.get("/stream")
def stream_data():
    def generate():
        for i in range(10):
            yield f"data: {i}\n\n"

    return StreamingResponse(
        generate(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache"},
    )

Returning Response objects directly gives full control over headers, status, and content type. Content-Disposition triggers a file download in browsers. StreamingResponse is useful for server-sent events and large file downloads.

Key points

Concepts covered

Custom Headers, Cache-Control, Pagination Headers, Content-Type, Response Object