Skip to content

Deprecation headers

By default the lifecycle is docs-only: a deprecated route is flagged in its per-version OpenAPI, but a client calling it gets no signal. Set deprecation_headers=True and RouterVersioner adds the Deprecation, Sunset and Link headers to every response of a route in its deprecation window; version_info (one VersionInfo per version) supplies the dates and guide URLs. Header values are computed once, at versionize() time. The only per-request work is prefixing the successor-version link with the request's root_path, exactly as the built-in /docs and /openapi.json routes do.

from datetime import date
from fastapi_router_versioning import RouterVersioner, VersionInfo

RouterVersioner(
    app=app,
    routers=router,
    deprecation_headers=True,
    version_info={
        (1, 0): VersionInfo(release_date=date(2024, 1, 15)),
        (2, 0): VersionInfo(
            release_date=date(2025, 3, 1),
            guide="https://api.example.com/docs/upgrade/v2",
        ),
        (3, 0): VersionInfo(release_date=date(2026, 1, 1)),
    },
).versionize()
Header Source Emitted when
Deprecation: @<unix-seconds> (RFC 9745) version_info[deprecate_in].release_date that version has a date
Sunset: <HTTP-date> (RFC 8594) version_info[remove_in].release_date remove_in is set and that version has a date
Link: …; rel="successor-version" (RFC 5829) the next mounted version still serving the same (path, method), root_path-prefixed such a version exists
Link: …; rel="deprecation" (RFC 9745) version_info[deprecate_in].guide that version has a guide URL

Both VersionInfo fields are optional; a version missing from the map, or a field left None, just yields nothing for that part, with no warning. With deprecation_headers=True and no version_info at all, only the successor-version link is emitted (it needs no config). versionize() raises ValueError if a route's remove_in date precedes its deprecate_in date (when both are dated): RFC 9745 forbids a Sunset earlier than the Deprecation.

This adds nothing to @api_version: the lifecycle stays on the route, the dates stay on the versioner. Deprecation and Sunset are only set if the response doesn't already carry them: a route that sets its own keeps it. The Link field is sent alongside any the route already emits (RFC 8288: multiple Link fields combine). See deprecation_headers_app.py.