OpenAPI 3.3 Proposal: API-Level Deprecation & Sunset Support #5193
Replies: 7 comments 7 replies
-
|
Hi friends, just a draft for now. Thoughts? |
Beta Was this translation helpful? Give feedback.
-
|
Sharing some of the comments I've made on the call:
Comments about the replacement section. I'd like to see an ability to point at an equivalent path item / operation within the same description. In both scenarios I outlined above, when we pull something, most of the times there's a better set of operations to use under the same API/version. Now if you factor in doc-gen or code gen, if we could mechanically point to the "replacement method people need to use in their code" (through standard doc comments semantics), it'd make the migration much easier for them. The process being:
Step 3 makes it much easier than having to open up the browser, or chat with the agent, to figure out what's the replacement operation. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
|
@miqui my main concern here is that at least part of this is about setting certain headers globally, and we have consistently refused to add such features and instead directed people to Overlays. It is more challenging than one might think to just set these globally, as you then need to have the context of which document is the entry document. This is already true for several things (Server Objects, Security Scheme Objects), but it's one of the trickier corners of the spec and we've tried to move away from it a bit. Although there is the "embedded overlay" idea (#5120). But not all of this is about global headers. I'd recommend focusing on the metadata aspect of it, as that will be less controversial, and we do have options (particularly Overlays) for setting the HTTP headers already. |
Beta Was this translation helpful? Give feedback.
-
|
I like the idea. it should follow the recommendations in these headers and add a redirect or location url for new services. Maybe reuse the link object? |
Beta Was this translation helpful? Give feedback.
-
|
just adding more ref data: Here is a breakdown of how the two headers differ and how they are used together: The Difference: Deprecation vs. Sunset
yntax and Usage Example: In this example: Deprecation: Tells the client the endpoint became deprecated on Feb 11, 2026. Sunset: Tells the client the endpoint will essentially "turn off" on Dec 31, 2026. Link: Provides a human-readable URL (via rel="deprecation") explaining why it's deprecated and what to use instead. |
Beta Was this translation helpful? Give feedback.
-
|
Here's the regenerated proposal. The core change from the feedback is a formal header mapping table and a worked example that walks through the exact conversion steps from OAS YAML fields to wire-format headers. Key design decisions:
# =============================================================================
# OpenAPI 3.3 Proposal: Rich Deprecation & Sunset Metadata
# =============================================================================
#
# Motivation
# ----------
# Today OAS uses `deprecated: true` — a bare boolean with no machine-readable
# dates, no sunset timeline, and no link to a migration guide. Tooling that
# wants to emit RFC 9745 (Deprecation) or RFC 8594 (Sunset) HTTP response
# headers must invent its own conventions or rely on vendor extensions.
#
# This proposal replaces the boolean with an optional Deprecation Object so
# that every field maps deterministically to an HTTP header value. An
# implementer reading this spec should be able to answer:
#
# "Given this metadata, what exact Deprecation, Sunset, and Link headers
# do I emit?"
#
# Header-Format Quick Reference
# -----------------------------
# Deprecation → RFC 9745 §2.1 → Structured Field Date (RFC 9651 §3.3.7)
# Wire format: Deprecation: @<unix-seconds>
#
# Sunset → RFC 8594 §3 → HTTP-date (IMF-fixdate, RFC 9110 §5.6.7)
# Wire format: Sunset: <day>, <DD> <Mon> <YYYY> HH:MM:SS GMT
#
# Link → RFC 8288 → Link: <url>; rel="deprecation"; type="…"
# Link: <url>; rel="successor-version"
# Link: <url>; rel="sunset"
#
# =============================================================================
openapi: "3.3.0"
info:
title: Acme Pet Store
version: 3.1.0
description: >
Demonstrates the proposed Deprecation Object on operations, parameters,
and schema properties — including the exact HTTP headers each example
produces.
# =============================================================================
# PATH EXAMPLES
# =============================================================================
paths:
# ---------------------------------------------------------------------------
# Example 1 — Fully-deprecated operation with all metadata
# ---------------------------------------------------------------------------
/v1/pets:
get:
operationId: listPets_v1
summary: List all pets (deprecated — use /v2/pets)
# ── NEW: Deprecation Object replaces `deprecated: true` ──────────────
#
# Backward compatibility:
# • `deprecated: true` still valid (boolean shorthand)
# • `deprecated: { ... }` new rich form
# Tooling MUST accept both. When the object form is present, the
# boolean `true` is implied.
#
deprecated:
# ── deprecatedAt ────────────────────────────────────────────────────
# REQUIRED when using the object form.
# An ISO 8601 date-time (the canonical interchange format in OAS).
#
# Header mapping → Deprecation: @<unix-epoch-seconds>
# Conversion rule: take the date-time, convert to Unix epoch
# seconds, and format as an SF-Date per RFC 9651 §3.3.7.
#
# Example output header:
# Deprecation: @1719705599
#
deprecatedAt: "2024-06-30T23:59:59Z"
# ── sunset ──────────────────────────────────────────────────────────
# OPTIONAL. ISO 8601 date-time.
# MUST NOT be earlier than `deprecatedAt` (per RFC 9745 §4).
#
# Header mapping → Sunset: <IMF-fixdate>
# Conversion rule: format the date-time as IMF-fixdate
# per RFC 9110 §5.6.7.
#
# Example output header:
# Sunset: Tue, 30 Jun 2025 23:59:59 GMT
#
sunset: "2025-06-30T23:59:59Z"
# ── description ─────────────────────────────────────────────────────
# OPTIONAL. Human-readable CommonMark explaining the deprecation.
# Not mapped to any header; rendered in documentation UIs.
#
description: >
`/v1/pets` was deprecated on 2024-06-30 and will become
unresponsive after 2025-06-30. Migrate to `GET /v2/pets`
which supports cursor-based pagination and filtering.
# ── documentation ───────────────────────────────────────────────────
# OPTIONAL. URL to a human-readable deprecation/migration guide.
#
# Header mapping → Link: <url>; rel="deprecation"; type="text/html"
# (per RFC 9745 §3.1)
#
# Example output header:
# Link: <https://developer.acme.com/migrations/pets-v1-to-v2>;
# rel="deprecation"; type="text/html"
#
documentation: https://developer.acme.com/migrations/pets-v1-to-v2
# ── successor ───────────────────────────────────────────────────────
# OPTIONAL. A URI or operation reference for the replacement.
#
# Header mapping → Link: <url>; rel="successor-version"
# (per RFC 8288)
#
# When the value is an absolute URI:
# Link: <https://api.acme.com/v2/pets>; rel="successor-version"
#
# When the value is an operationId (starts without scheme),
# tooling SHOULD resolve it to the operation's first server URL +
# path and emit the Link header accordingly.
#
successor: https://api.acme.com/v2/pets
responses:
"200":
description: A list of pets.
# ── What the gateway / middleware MUST emit ────────────────────────
# Given the metadata above, a conforming implementation produces
# these response headers:
#
# Deprecation: @1719705599
# Sunset: Mon, 30 Jun 2025 23:59:59 GMT
# Link: <https://developer.acme.com/migrations/pets-v1-to-v2>;
# rel="deprecation"; type="text/html",
# <https://api.acme.com/v2/pets>; rel="successor-version"
#
# (Multiple Link values MAY be combined in a single header field
# per RFC 8288 §3.)
#
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Pet"
# ---------------------------------------------------------------------------
# Example 2 — Future deprecation (date is in the future)
# ---------------------------------------------------------------------------
/v2/pets:
get:
operationId: listPets_v2
summary: List all pets (v2 — will be deprecated 2026-03-01)
deprecated:
deprecatedAt: "2026-03-01T00:00:00Z"
sunset: "2026-09-01T00:00:00Z"
description: >
v2 will be deprecated on 2026-03-01 in favor of v3 which
adds server-sent events for real-time updates.
documentation: https://developer.acme.com/migrations/pets-v2-to-v3
successor: https://api.acme.com/v3/pets
# ── Resulting headers (emitted NOW, before deprecatedAt) ──────────────
#
# Even before the deprecation date, the server SHOULD emit the headers
# so consumers can discover the upcoming change (RFC 9745 §2.1 allows
# future dates).
#
# Deprecation: @1772438400
# Sunset: Tue, 01 Sep 2026 00:00:00 GMT
# Link: <https://developer.acme.com/migrations/pets-v2-to-v3>;
# rel="deprecation"; type="text/html",
# <https://api.acme.com/v3/pets>; rel="successor-version"
#
responses:
"200":
description: A list of pets.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Pet"
# ---------------------------------------------------------------------------
# Example 3 — Boolean shorthand (backward compatible)
# ---------------------------------------------------------------------------
/v1/pets/{petId}/toys:
get:
operationId: listPetToys_v1
summary: List toys for a pet (legacy)
# Boolean form still works — means "deprecated, date unknown."
# Tooling SHOULD NOT emit a Deprecation header when no date is
# available, since RFC 9745 requires an SF-Date value.
# Tooling SHOULD still render the operation as deprecated in docs.
deprecated: true
responses:
"200":
description: A list of toys.
# ---------------------------------------------------------------------------
# Example 4 — Deprecated parameter
# ---------------------------------------------------------------------------
/v2/pets/{petId}:
get:
operationId: getPet_v2
summary: Get a pet by ID
parameters:
- name: petId
in: path
required: true
schema:
type: string
- name: fields
in: query
description: Comma-separated field list (deprecated — use `select` instead)
# Deprecation Object on a Parameter Object
#
# Parameters don't map to their own HTTP resource, so the
# Deprecation/Sunset headers are NOT emitted for individual
# parameter deprecations. These metadata are for documentation
# and SDK generation only.
#
# SDK generators SHOULD mark the parameter with a language-
# appropriate deprecation annotation (e.g., @Deprecated in Java,
# deprecated() wrapper in Python, #[deprecated] in Rust).
#
deprecated:
deprecatedAt: "2025-01-15T00:00:00Z"
description: >
Use the `select` parameter instead, which supports
JSONPath expressions for field selection.
schema:
type: string
- name: select
in: query
description: JSONPath expression for field selection.
schema:
type: string
responses:
"200":
description: A single pet.
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
# =============================================================================
# SCHEMA EXAMPLES — deprecated properties
# =============================================================================
components:
schemas:
Pet:
type: object
required: [id, name]
properties:
id:
type: string
format: uuid
name:
type: string
tag:
type: string
# ── Deprecated schema property ──────────────────────────────────────
# Schema-level deprecations are for documentation and SDK/codegen
# only — they do NOT produce HTTP headers (there is no per-field
# HTTP resource to attach them to).
#
# Codegen SHOULD emit language deprecation annotations on the
# generated property accessor/field.
#
status:
type: string
enum: [available, pending, sold]
deprecated:
deprecatedAt: "2024-09-01T00:00:00Z"
sunset: "2025-09-01T00:00:00Z"
description: >
The `status` field is replaced by the `availability` object
which supports richer state modeling (e.g., reserved, hold).
availability:
$ref: "#/components/schemas/Availability"
Availability:
type: object
properties:
state:
type: string
enum: [available, reserved, hold, pending, sold]
since:
type: string
format: date-time
# =============================================================================
#
# FORMAL HEADER MAPPING RULES
# (Normative — to be included in the OAS 3.3 specification text)
#
# =============================================================================
#
# When a Deprecation Object is present on an Operation Object, a conforming
# server or gateway SHOULD emit the following HTTP response headers on every
# response to that operation:
#
# ┌─────────────────────┬────────────────────────────────────────────────────┐
# │ OAS Field │ HTTP Header(s) Produced │
# ├─────────────────────┼────────────────────────────────────────────────────┤
# │ deprecatedAt │ Deprecation: @<epoch> │
# │ (ISO 8601 datetime) │ where <epoch> = Unix seconds of deprecatedAt │
# │ │ per RFC 9651 §3.3.7 (Structured Field Date) │
# │ │ per RFC 9745 §2.1 │
# ├─────────────────────┼────────────────────────────────────────────────────┤
# │ sunset │ Sunset: <IMF-fixdate> │
# │ (ISO 8601 datetime) │ formatted per RFC 9110 §5.6.7 │
# │ │ per RFC 8594 §3 │
# ├─────────────────────┼────────────────────────────────────────────────────┤
# │ documentation │ Link: <url>; rel="deprecation"; type="text/html" │
# │ (URI) │ per RFC 9745 §3.1 + RFC 8288 │
# ├─────────────────────┼────────────────────────────────────────────────────┤
# │ successor │ Link: <url>; rel="successor-version" │
# │ (URI) │ per RFC 8288 │
# ├─────────────────────┼────────────────────────────────────────────────────┤
# │ description │ (none — documentation UI only) │
# ├─────────────────────┼────────────────────────────────────────────────────┤
# │ deprecated: true │ (none — no date available; docs only) │
# │ (boolean shorthand) │ Tooling SHOULD NOT emit a Deprecation header │
# │ │ without a date, as RFC 9745 requires SF-Date. │
# └─────────────────────┴────────────────────────────────────────────────────┘
#
# When the Deprecation Object appears on a Parameter Object or Schema
# property, headers are NOT emitted (there is no per-field HTTP resource).
# The metadata is consumed by documentation renderers and SDK generators.
#
# Validation constraints:
# • sunset MUST NOT be earlier than deprecatedAt (RFC 9745 §4).
# • deprecatedAt MUST be a valid ISO 8601 date-time string.
# • sunset, if present, MUST be a valid ISO 8601 date-time string.
# • documentation, if present, MUST be a valid URI (RFC 3986).
# • successor, if present, MUST be a valid URI (RFC 3986).
#
# =============================================================================
#
# WORKED EXAMPLE: Header Construction
#
# Given this operation metadata:
#
# deprecated:
# deprecatedAt: "2024-06-30T23:59:59Z"
# sunset: "2025-06-30T23:59:59Z"
# documentation: https://developer.acme.com/migrations/pets-v1-to-v2
# successor: https://api.acme.com/v2/pets
#
# Step 1 — Deprecation header
# Convert "2024-06-30T23:59:59Z" to Unix epoch seconds:
# Date.parse("2024-06-30T23:59:59Z") / 1000 = 1719705599
# Emit:
# Deprecation: @1719705599
#
# Step 2 — Sunset header
# Convert "2025-06-30T23:59:59Z" to IMF-fixdate:
# new Date("2025-06-30T23:59:59Z").toUTCString()
# → "Mon, 30 Jun 2025 23:59:59 GMT"
# Emit:
# Sunset: Mon, 30 Jun 2025 23:59:59 GMT
#
# Step 3 — Link headers
# Emit:
# Link: <https://developer.acme.com/migrations/pets-v1-to-v2>;
# rel="deprecation"; type="text/html",
# <https://api.acme.com/v2/pets>; rel="successor-version"
#
# Full response headers on every response to GET /v1/pets:
#
# HTTP/1.1 200 OK
# Content-Type: application/json
# Deprecation: @1719705599
# Sunset: Mon, 30 Jun 2025 23:59:59 GMT
# Link: <https://developer.acme.com/migrations/pets-v1-to-v2>;
# rel="deprecation"; type="text/html",
# <https://api.acme.com/v2/pets>; rel="successor-version"
#
# [ { "id": "abc-123", "name": "Fido" }, ... ]
#
# =============================================================================
#
# TEST MATRIX (the tests that "almost write themselves")
#
# These assertions can be used by OAS validators, gateway conformance
# suites, and SDK test harnesses:
#
# | # | Input (OAS YAML) | Expected Header Output |
# |---|-------------------------------------------|-----------------------------------------------|
# | 1 | deprecatedAt: "2024-06-30T23:59:59Z" | Deprecation: @1719705599 |
# | 2 | sunset: "2025-06-30T23:59:59Z" | Sunset: Mon, 30 Jun 2025 23:59:59 GMT |
# | 3 | documentation: https://example.com/guide | Link: <https://example.com/guide>; |
# | | | rel="deprecation"; type="text/html" |
# | 4 | successor: https://api.example.com/v2/foo | Link: <https://api.example.com/v2/foo>; |
# | | | rel="successor-version" |
# | 5 | deprecated: true (boolean) | (no Deprecation header emitted) |
# | 6 | sunset earlier than deprecatedAt | VALIDATION ERROR |
# | 7 | deprecatedAt + sunset + doc + successor | All four headers combined (see worked example)|
# | 8 | deprecatedAt only, no sunset | Deprecation header only, no Sunset |
# | 9 | Deprecated parameter | No HTTP headers (docs/codegen only) |
# |10 | Deprecated schema property | No HTTP headers (docs/codegen only) |
#
# ============================================================================= |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem Statement
Currently, OpenAPI only supports deprecation at the operation level via the deprecated: boolean field on Operation Objects. There is no standard way to signal that an entire API is being deprecated or sunset.
This forces API providers to:
Proposed Solution
Add formal lifecycle management fields to OpenAPI, aligned with existing standards:
Two Approaches
Approach A: New Root-Level
lifecycleObjectPros:
Cons:
Approach B: Extend Info Object (Minimal Change)
Pros
deprecated: truemirrors operation-level patternCons
Proposed Schema Additions
Lifecycle Object (Approach A)
stageactive,deprecated,sunset,deactivatedstageDatedeprecationsunsetresponseHeadersDeprecation Object
datereasonmigrationreplacementcontactSunset Object
datepolicygracePeriodInfo Object Extensions (Approach B)
deprecateddeprecationDatesunsetDatesuccessorVersiondeprecationInfoBeta Was this translation helpful? Give feedback.
All reactions