api-versioning-policy
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Versioning Policy
API 版本管理策略
You are an API lifecycle strategist. A written versioning and deprecation policy covers:
- Which scheme carries the version.
- What counts as a breaking change.
- How long consumers get before a version dies.
- How you communicate and enforce that death.
- Who signs off on breaking anything.
The hard half is not choosing a scheme - "choosing a versioning strategy is the easy half. The hard half is removing a version without breaking the consumers still on it."
This skill owns the policy: it decides what the scheme and its lifecycle rules are. Sibling only checks at review time that a scheme exists and is applied consistently.
samber/developer-platform-skills@public-api-design-reviewThe wire-level contract is a different versioning surface from the client SDKs wrapping it. SDKs version under plain SemVer, and Stripe is the citable illustration of the split - a date-versioned, account-pinned API next to SemVer SDKs whose major-bump trigger is stricter than the API's version rule. Write the SDK policy separately; this skill covers the contract.
你是 API 生命周期策略师。一份书面的版本管理与弃用策略需要覆盖:
- 由哪种方案承载版本号。
- 什么算破坏性变更。
- 消费者在某个版本消亡前有多长时间。
- 你如何传达并执行这一消亡。
- 谁有权批准任何破坏性变更。
困难的一半并不是选择方案——“选择版本策略是容易的一半;困难的一半是在不破坏仍在使用旧版本的消费者的情况下移除一个版本。”
本技能负责策略本身:它决定采用什么方案及其生命周期规则。兄弟技能 只在评审时检查是否确实存在某个方案并被一致应用。
samber/developer-platform-skills@public-api-design-review线上传输契约(wire-level contract)与包装它的客户端 SDK 是两个不同的版本管理面。SDK 按普通 SemVer 进行版本管理;Stripe 是一个可引用的典型案例——API 采用基于日期、账户固定的版本,而 SDK 采用 SemVer,且其主版本触发条件比 API 的版本规则更严格。请单独编写 SDK 策略;本技能覆盖的是线上契约部分。
Interview
访谈
This is a strategy decision - interview before proposing anything. Ask one question per message, multiple-choice where offered, in this order. Question 1 comes first because its answer changes which later sections even apply.
- Paradigm: REST/HTTP, GraphQL, gRPC, or several surfaces? (REST defaults to version-and-sunset; GraphQL defaults to continuous schema evolution - see step 1.)
- Audience: internal only, partner/B2B under contract, fully public, or a mix? (This drives the notice period more than anything else - see step 4.)
- Current state: greenfield, one unversioned live API, or multiple versions already live? If versions exist: which scheme, and is any version both deprecated and still the default?
- Breaking-change pressure: how many breaking changes shipped or were blocked in the past year - none, one or two, roughly quarterly, or monthly-plus?
- Consumer reachability: can you identify and directly contact every consumer? Do any consumers ship mobile apps or run enterprise change-management cycles?
- Deadline: by what date must this policy - or the pending breaking change forcing it - land?
- One-off or standing: is this about retiring one version, or a policy the platform will operate for years?
- Effort ceiling: how much engineering time for versioning machinery, headcount for migration communication, and political capital for governance can you actually spend?
Questions 6-8 exist because the scheme options diverge sharply on time-to-effect, durability, and effort - the ranking in step 3 cannot be picked without them. A hard deadline promotes the low-machinery rungs; a years-long standing policy promotes the expensive ones.
If your harness has persistent memory, store the finished policy's core decisions (scheme, breaking-change definition, notice windows, enforcement style) so later deprecation runs start from the policy instead of re-deriving it.
这是一个策略决策——在提出任何建议前先访谈。每条消息问一个问题,提供选项时用多选,按以下顺序。问题 1 放在最前,因为它的答案会决定后续哪些章节适用。
- 范式: REST/HTTP、GraphQL、gRPC,还是多种接口面?(REST 默认采用“版本+下线”;GraphQL 默认采用持续 schema 演进——见步骤 1。)
- 受众: 仅内部、合同约束的合作伙伴/B2B、完全公开,还是混合?(这一点对通知期限的影响最大——见步骤 4。)
- 当前状态: 全新项目、只有一个未版本化的在线 API,还是已有多个线上版本?如果已有版本:采用什么方案,是否存在某个版本既被弃用仍是默认版本?
- 破坏性变更压力: 过去一年有多少破坏性变更已发布或被阻止——没有、一两个、大约每季度,还是每月不止一个?
- 消费者可达性: 你能识别并直接联系每一个消费者吗?是否有消费者发布移动应用,或运行企业变更管理流程?
- 截止日期: 这份策略——或者说迫使你制定它的待处理破坏性变更——必须最晚在什么时候落地?
- 一次性还是长期: 这是要退役某一个版本,还是一项平台将运行多年的长期策略?
- 投入上限: 你实际能投入多少工程时间来建设版本管理机制、多少人力来做迁移沟通、多少政治资本用于治理?
问题 6-8 之所以存在,是因为不同方案在见效速度、耐久性和投入上差异巨大——没有这些信息就无法在步骤 3 中做排序。硬性截止日期会推动你选择低机制成本的档位;多年长期策略则会推动你选择高投入的档位。
如果你的运行环境有持久记忆,请存储最终策略的核心决策(方案、破坏性变更定义、通知窗口、执行方式),以便后续的弃用流程从该策略出发,而不是重新推导。
Audience split: internal / partner / public
受众划分:内部 / 合作伙伴 / 公开
The split that drives every number in this policy is consumer reachability: the harder it is to identify and contact every consumer, the longer and more conservative the policy must be.
- Internal consumers get coordinated deploys and contract tests instead of notice periods.
- Partner consumers get negotiated SLAs.
- Fully public consumers get 12-24 months, because mass communication is all you have.
This is why a hyperscaler and a mid-size platform land on the same ~12-month public floor while their internal APIs move in days.
驱动本策略中每个数字的关键维度是消费者可达性:识别并联系每一个消费者越难,策略就必须越保守、周期越长。
- 内部消费者:采用协调部署和契约测试,而不是通知期。
- 合作伙伴消费者:通过协商的 SLA 来约定。
- 完全公开的消费者:给 12-24 个月,因为你只能靠大规模公告触达。
这就是为什么一家超大规模云厂商和一家中型平台会落在相同的约 12 个月公开下限上,而它们的内部 API 可以在数天内完成变更。
Workflow
工作流程
- Settle the paradigm - versioning vs continuous evolution.
- Write the breaking-change definition.
- Choose the version scheme.
- Set the notice windows and concurrent-version count.
- Design the sunset communication.
- Choose the enforcement style at the sunset date.
- Stand up governance proportionate to blast radius.
- Brainstorm candidate policies, then draft and validate the policy document.
- 确定范式——版本化 vs 持续演进。
- 编写破坏性变更定义。
- 选择版本方案。
- 设定通知窗口和并存版本数量。
- 设计下线沟通方案。
- 选择下线日期的执行方式。
- 按爆炸半径建立相应的治理机制。
- 头脑风暴候选策略,然后起草并验证策略文档。
1. Settle the paradigm
1. 确定范式
REST and GraphQL answer breaking change with different policies, not flavors of one:
- REST/HTTP: version-and-sunset. Ship a new version consumers explicitly opt into, deprecate the old one, retire it on a schedule. Steps 3-6 apply in full.
- GraphQL: continuous evolution. One continuously live schema; clients receive only the fields they request. Add a field, mark the old one , and remove it once telemetry shows zero real consumers. graphql.org states the position outright: GraphQL "takes a strong opinion on avoiding versioning by providing the tools for the continuous evolution of a GraphQL schema."
@deprecated(reason: "...") - The caveat. Apollo's caveat is the part teams miss:
@deprecated"is a documentation and tooling signal, not a removal mechanism" - removal still needs usage monitoring and a written field-rollover strategy. For a GraphQL API, skip step 3's scheme menu and apply steps 4-7 field-by-field instead of version-by-version.@deprecated - Neither is paradigm-locked. Shopify date-versions its GraphQL Admin API (quarterly, same scheme as its REST surface) - a large production counter-example to "GraphQL means no versioning." Zalando's REST guidelines explicitly discourage versioning in favor of additive-only evolution. The paradigm sets the default policy; it does not dictate it.
YYYY-MM - gRPC: the breaking-change definition, notice windows, and governance (steps 2, 4, 7) apply unchanged; the scheme menu and HTTP-header communication mechanics in steps 3 and 5 are REST-specific. The gRPC-native equivalents - major version encoded in the proto package (AIP-185), buf breaking-change gates, proto deprecation options - are sibling 's territory; use it for the scheme and enforcement layer and keep this skill for the policy layer.
samber/developer-platform-skills@public-grpc-api-design
REST 和 GraphQL 面对破坏性变更时的答案是两种不同策略,而不是同一策略的两种变体:
- REST/HTTP:版本化 + 下线。 发布一个消费者显式选择接入的新版本,弃用旧版本,并按计划退役。步骤 3-6 完整适用。
- GraphQL:持续演进。 一个持续在线的 schema;客户端只收到它们请求的字段。添加新字段,将旧字段标记为 ,并在遥测显示真实消费者为零后将其移除。graphql.org 直接表明了立场:GraphQL “对避免版本化采取了强烈立场,通过提供工具来实现 GraphQL schema 的持续演进。”
@deprecated(reason: "...") - 的注意事项。 Apollo 提出的注意点是团队经常遗漏的部分:
@deprecated“是一个文档和工具信号,而不是移除机制”——移除仍然需要用量监控和书面的字段迁移策略。对于 GraphQL API,跳过步骤 3 的方案菜单,将步骤 4-7 按字段而非按版本应用。@deprecated - 两种范式都没有被锁死。 Shopify 对其 GraphQL Admin API 使用基于日期的方式做版本管理(每季度,与其 REST 接口面方案相同)——这是反驳“GraphQL 就意味着不版本化”的大型生产环境反例。Zalando 的 REST 指南则明确不鼓励版本化,倾向于纯增量演进。范式设定默认策略,但并不强制规定。
YYYY-MM - gRPC: 破坏性变更定义、通知窗口和治理(步骤 2、4、7)原样适用;步骤 3 的方案菜单和步骤 5 的 HTTP 头沟通机制是 REST 特有的。gRPC 原生的对应物——在 proto 包中编码主版本(AIP-185)、buf 破坏性变更门禁、proto 弃用选项——属于兄弟技能 的领域;请用它来处理方案和执行层,本技能负责策略层。
samber/developer-platform-skills@public-grpc-api-design
2. Write the breaking-change definition
2. 编写破坏性变更定义
A deprecation policy without a written breaking-change definition is unenforceable - every dispute becomes a judgment call. Write both lists into the policy:
- Breaking (requires a new version): any change that makes a previously working client fail.
- Removing or renaming a field or endpoint.
- Changing a field's type.
- Removing an enum value.
- Adding a required request parameter.
- Changing the status code or content type for the same scenario.
- Non-breaking (ships into the current version): Stripe's published list is a citable starting taxonomy.
- Adding new resources.
- Adding new optional request parameters.
- Adding new response properties.
- Changing property order.
- Changing the length or format of opaque strings (including ID prefixes).
- Adding new webhook event types.
- Open enums cut version churn: declare which enum fields may grow new values without a version bump, shifting tolerance onto callers instead of onto your compatibility machinery - Stripe documents this as an explicit backward-compatible carve-out.
- A security escape hatch, scoped narrowly: LinkedIn's shape is the standard - even a strict minimum-window policy reserves the right to patch any version "for any critical security, privacy issues, or bug fixes." Write the exception down; never invoke an unwritten one.
Present the definition as a pragmatic simplification, not settled theory - the debate is real and your policy should survive a reader who has seen it.
- Jeremy Ashkenas: "if your package has a minor change in behavior that will 'break' for 1% of your users, is that a breaking change?"
- Nate Meyvis: "the very notion of a breaking change is relative to an API, and if the API doesn't exist [as a published contract], the notion is not well-defined" - which is precisely why the definition must be published.
- Hyrum's Law: with enough users, every observable behavior is depended on regardless of contract. Declare what is explicitly not contractual (message text, ordering, timing).
The operational escape from the theory: watch breaking-change frequency. If you need a breaking change more than about twice a year, the scheme or the compatibility discipline is wrong - that is answerable from telemetry regardless of which side of the debate the team leans toward.
没有书面破坏性变更定义的弃用策略是无法执行的——每一次争议都会变成主观判断。请把以下两类清单都写进策略:
- 破坏性(需要新版本): 任何会让原本可用的客户端失败的变化。
- 删除或重命名字段或端点。
- 改变字段类型。
- 删除枚举值。
- 新增必填请求参数。
- 对同一场景改变状态码或内容类型。
- 非破坏性(可直接进入当前版本): Stripe 已发布的清单是一个可引用的起点分类。
- 新增资源。
- 新增可选请求参数。
- 新增响应属性。
- 改变属性顺序。
- 改变不透明字符串的长度或格式(包括 ID 前缀)。
- 新增 webhook 事件类型。
- 开放枚举可以减少版本更替:声明哪些枚举字段可以在不升级版本的情况下增加新值,把容忍负担转移给调用方,而不是你的兼容机制——Stripe 将这一点作为明确的向后兼容例外记录。
- 范围严格限定的安全逃生舱口: LinkedIn 的做法是标准形态——即使是最严格的最短窗口策略,也保留“出于任何关键安全、隐私问题或 bug 修复”修补任何版本的权利。请把这个例外写下来;永远不要调用一个没有写下来的例外。
请把该定义呈现为务实的简化,而不是定论——相关争论是真实存在的,你的策略应该经得起看过这些争论的读者审视。
- Jeremy Ashkenas: “如果你的包有一个次要行为变化,会对 1% 的用户造成‘破坏’,这算破坏性变更吗?”
- Nate Meyvis: “破坏性变更这个概念本身就是相对于 API 而言的;如果 API 不存在(作为已发布的契约),这个概念就无从定义”——这正是定义必须发布的原因。
- Hyrum 定律(Hyrum's Law): 当用户足够多时,无论契约如何,每一个可观察行为都会被依赖。请明确声明哪些内容_不是_契约的一部分(消息文本、顺序、时序)。
从理论中走出的可操作出路:关注破坏性变更频率。如果你每年需要的破坏性变更超过大约两次,那么方案或兼容性纪律就有问题——这可以通过遥测来回答,无论团队在争论中倾向于哪一边。
3. Choose the version scheme
3. 选择版本方案
Five candidate schemes for a REST surface. Ranked - say it out loud, don't leave it to row order:
-
effort:
date-based account-pinned > no-versioning > header > URI == query -
value:
date-based account-pinned > no-versioning > URI > header > query -
efficiency (value per unit of effort):
URI > header > no-versioning > query > date-based account-pinned -
Default rung: URI major version (). Trivially visible, testable in a browser, cacheable per-version, demanding zero header sophistication from integrators - which is why it stays the working default for most public APIs despite Roy Fielding's objection (below). URI and query tie on effort because both are an afternoon of routing; query ranks last on value because it pollutes the query string and integrators rarely expect it there.
/v1/users -
Move up to header versioning (a version or date in a request header, GitHub/LinkedIn-style) when URI stability matters to you or versions ship on a calendar cadence. Two opposite defaults exist within it and the policy must pick one: Stripe pins accounts to a version silently; LinkedIn refuses unversioned calls with an error. LinkedIn's choice is safer - a required version can never drift under a caller silently.
YYYY-MM-DD -
No-versioning (additive-only evolution) is a real rung, not an absence of policy: Zalando runs it for REST. Its cost is permanent - every change must be additive forever, and you need usage telemetry before removing anything. It fits reachable consumer bases with strong review discipline.
-
The starved option: date-based, account-pinned versioning (Stripe).
- Highest value on the board: Stripe has "maintained compatibility with every version of our API since the company's inception in 2011."
- Highest effort too: a gate-and-transformer chain where the engine computes only the latest shape, and per-version transformers downgrade each response, one module per historical breaking change, maintained forever.
- It loses every efficiency round. Two conditions promote it anyway: breaking changes wanted many times a year, and a long tail of integrators you cannot force to migrate.
- Weigh it against your actual surface size and change frequency; never adopt it reflexively because Stripe does it.
-
Deleted, not demoted: no-versioning, when the consumers are unreachable and breaking changes already ship quarterly or faster. Interview answers Q5 and Q4 together rule additive-only evolution out - it promises forever what the change rate is already breaking, against a base you cannot warn. Strike it from the menu rather than parking it last, because "we'll just be careful and stay additive" reappears as the plan every time the version-machinery estimate lands. Re-promotion trigger: breaking-change frequency back under roughly twice a year, or a consumer base you can enumerate and contact.
-
Fielding's objection, named because a colleague will raise it: the REST dissertation's author holds that the best practice for versioning is "don't" - an evolvable architecture shouldn't need a version number, and a version in the path changes the URL of a resource that hasn't changed. The no-versioning rung is the honest response to that argument; the URI default is the pragmatic one.
This ranking is a default, not a law. Re-rank against the interview:
- A hard deadline (Q6) promotes URI.
- A years-long standing policy with high change pressure (Q4, Q7) promotes date-based.
- A team already running per-account configuration infrastructure gets Stripe-style pinning cheaper than the effort line assumes.
If Q1 answered GraphQL, this menu does not apply as a ladder at all - continuous evolution is the paradigm's own default policy, an argued opt-out of the ranking, not a hidden sixth rung. Shopify's date-versioned GraphQL API is the documented way back, if one unified versioning story across surfaces matters more.
Whatever wins: never allow unversioned access as a fallback path. Discord permitted it and developers named it directly as the anti-pattern - removing the unversioned path later is itself a breaking change nobody signed up for.
REST 接口面有五个候选方案。按排名来——请大声说出来,不要只依赖列表顺序:
-
投入(effort):
date-based account-pinned > no-versioning > header > URI == query -
价值(value):
date-based account-pinned > no-versioning > URI > header > query -
效率(单位投入的价值):
URI > header > no-versioning > query > date-based account-pinned -
默认档位:URI 主版本()。肉眼可见、可在浏览器中测试、可按版本缓存、不要求集成方理解任何 Header 机制——这就是为什么尽管 Roy Fielding 反对(见下文),它仍是大多数公开 API 的实用默认选择。URI 和 query 在投入上并列,因为两者都只是花一个下午做路由;query 在价值上垫底,因为它污染查询字符串,集成方也很少预期版本会出现在那里。
/v1/users -
升级到 Header 版本化(在请求 Header 中携带版本或日期,GitHub/LinkedIn 风格)——当你关心 URI 稳定性,或版本按日历节奏发布时。其中存在两种相反的默认实践,策略必须二选一:Stripe 静默地把账户固定到某个版本;LinkedIn 对未带版本的请求直接报错。LinkedIn 的选择更安全——必填版本永远不会在调用方不知情的情况下悄悄漂移。
YYYY-MM-DD -
不版本化(纯增量演进) 是一个真实存在的档位,而不是没有策略:Zalando 在 REST 上就是这么做的。它的代价是永久的——每一次变更都必须永远保持增量,并且在移除任何东西之前你都需要用量遥测。它适合消费者基群可触达且评审纪律强的团队。
-
被低估的选项:基于日期、账户固定的版本化(Stripe)。
- 价值最高:Stripe 声称“自公司 2011 年成立以来,一直与 API 的每一个版本保持兼容。”
- 投入也最高:一条“门禁 + 转换器”链条——引擎只计算最新形态,每个历史破坏性变更对应一个按版本转换的模块,将每个响应降级为旧形态,并且需要永远维护。
- 它在每一轮效率比较中都会输。但有两个条件仍会推动你选择它:一年内多次需要破坏性变更,以及存在你无法强制迁移的长尾集成方。
- 请结合你实际的接口面规模和变更频率来权衡;永远不要因为 Stripe 这么做就条件反射地采用。
-
删除而不是降级:当消费者不可触达、且破坏性变更已经按季度或更快发布时——不版本化。 访谈中 Q5 和 Q4 的答案合起来会排除纯增量演进——它承诺的“永远兼容”正在被真实变更速度打破,而且你面对的是一个无法预警的基群。请把它从菜单中划掉,而不是排在最后,因为每次版本机制的成本估算出来后,“我们会小心一些、保持增量”这个计划就会重新冒出来。重新提升它的触发条件:破坏性变更频率回到大约每年两次以下,或者消费者基群可以枚举并联系。
-
Fielding 的反对意见,之所以要明确写出,是因为一定会有同事提出: REST 学位论文的作者认为版本化的最佳实践是“不要版本化”——一个可演进的架构不应该需要版本号,路径中的版本号会改变一个并未发生变化的资源的 URL。不版本化档位是对这一论点的诚实回应;URI 默认则是务实的回应。
这个排名是默认值,不是法则。请根据访谈结果重新排名:
- 硬性截止日期(Q6)会推动 URI。
- 变更压力大、且需要运行多年的长期策略(Q4、Q7)会推动基于日期的方式。
- 如果一个团队已经在运行按账户配置的基础设施,Stripe 式固定版本的成本会比投入曲线假设的更低。
如果 Q1 的答案是 GraphQL,这个菜单根本不适合作为阶梯——持续演进是这种范式自己的默认策略,是对这个排名的有意退出,而不是隐藏的第六档。Shopify 基于日期的 GraphQL API 是有据可查的回归路径——如果你更看重跨接口面统一版本管理叙事的话。
无论哪个方案胜出:永远不要允许未版本化访问作为回退路径。Discord 允许过这一点,开发者直接将其指认为反模式——之后移除未版本化路径本身就是一个没有人主动同意过的破坏性变更。
4. Set the notice windows and concurrent-version count
4. 设定通知窗口和并存版本数量
Set the numbers from audience (interview Q2/Q5), then check them against the published benchmarks in references/notice-period-benchmarks.md:
- Fully public API: at least 12 months of deprecation notice - the floor Google Cloud and Shopify converge on.
- Raise to at least 24 months when consumers ship mobile apps or are enterprises - Microsoft Graph and Meta's floor; app-store update cycles and enterprise change management are slow and outside your control.
- Partner/B2B API: put the number in the contract or SLA, not on a public policy page - it is negotiable per partner and enforceable when written there.
- Internal API: replace the notice period with consumer-driven contract testing and coordinated deploys. A notice period compensates for not being able to coordinate with consumers directly; internal teams can.
- Concurrent versions: cap at 2-3 active (the cross-source consensus). Every additional live version multiplies maintenance, testing, and documentation cost. Shopify sustains ~4 by strict quarterly automation; Salesforce's dozens are the cautionary accumulation.
- Define when the clock starts - version release, next version's release, or deprecation announcement. Meta's 2-year guarantee starts when the next version ships, a subtlety that moves real dates by months; ambiguity here becomes a support dispute later.
- Publish dates as commitments, and treat published benchmarks as floors. Salesforce slipped a retirement two years; Twilio extended one EOL three times. Slipping is survivable but costly - it teaches integrators that "final" dates aren't final. Cite external benchmarks as "at least X months," never "exactly X."
根据受众(访谈 Q2/Q5)确定数字,然后对照 references/notice-period-benchmarks.md 中发布的基准进行检查:
- 完全公开 API:至少 12 个月的弃用通知期——这是 Google Cloud 和 Shopify 共同收敛到的下限。
- 当消费者发布移动应用或属于企业时,提高到至少 24 个月——Microsoft Graph 和 Meta 的下限;应用商店更新周期和企业变更管理都很慢,且不受你控制。
- 合作伙伴/B2B API: 把数字写进合同或 SLA,而不是写在公开策略页上——它可以在每个合作伙伴间协商,写进合同后才可以执行。
- 内部 API: 用消费者驱动的契约测试和协调部署取代通知期。通知期是用来弥补无法直接与消费者协调的;内部团队可以协调。
- 并存版本:活跃版本上限设为 2-3 个(跨来源共识)。每多一个在线版本都会成倍增加维护、测试和文档成本。Shopify 依靠严格的季度自动化维持约 4 个版本;Salesforce 的几十个版本则是警示性的累积。
- 明确计时起点——是版本发布、下一个版本发布,还是弃用公告。Meta 的两年保证是从_下一个_版本发布时开始计算,这个细节会让实际日期相差数月;这里的模糊之处日后会演变成支持纠纷。
- 把日期作为承诺发布,并把公开基准视为下限。 Salesforce 曾将一次退役推迟两年;Twilio 曾三次延长一个 EOL。延期是可以扛过去的,但代价高昂——它教会集成方“最终”日期并不是最终的。引用外部基准时要说“至少 X 个月”,绝不要说“正好 X 个月”。
5. Design the sunset communication
5. 设计下线沟通方案
Deprecation is a staged process, not an event. Never skip from announce to removed.
- Announce
- Deprecate - both versions serving, signals live.
- Sunset - date arrives.
- Removal - grace period ends.
- Ship the header first (RFC 9745, Standards Track, March 2025). It signals deprecation is coming or here, and can ship before any sunset date exists - the RFC is explicit that it changes no behavior.
Deprecation - Add the header once the date is firm (RFC 8594 - Informational, not Standards Track; cite it accordingly). It carries the exact timestamp the version stops responding, on the deprecated version's responses only.
Sunset - Attach (RFC 8288) so tooling finds the replacement without scraping prose, and mark
Link: rel="successor-version"in the OpenAPI document so docs and codegen surface it.deprecated: true - Never rely on headers alone. Most APIs don't use these headers and "the reasons are mostly cultural" - which means most integrators' tooling won't surface them either. Pair headers with at least one channel that reaches humans: changelog, developer-dashboard banner, email targeted at accounts still calling the deprecated version, or in-response warnings (Slack embeds deprecation notices in API response payloads - it reaches exactly the callers still affected, cheaper than a mass campaign).
- Own the migration, not just the announcement. The Churn Rule: if you own the thing being deprecated, you own migrating your consumers - a migration guide with concrete steps, tooling where feasible, and support channels. Announcing a sunset and leaving integrators to figure it out is a named failure mode, not a middle ground. Compulsory deprecation (a hard deadline) obligates migration support; advisory deprecation (warnings only, consumers move on their own timeline) is the default until maintenance cost or security risk justifies forcing the issue.
Deprecation communication overlaps with sibling - the delineation: this skill owns the planned, months-long version-lifecycle communication; that one owns incident and status-page communication when the platform is currently broken.
samber/developer-platform-skills@api-status-communication弃用是一个分阶段的过程,而不是一个事件。永远不要从“公告”直接跳到“移除”。
- 公告(Announce)
- 弃用(Deprecate)——两个版本都在服务,信号已上线。
- 下线(Sunset)——日期到达。
- 移除(Removal)——宽限期结束。
- 先发送 头(RFC 9745,标准轨道,2025 年 3 月)。它表示弃用即将到来或已经到来,并且可以在任何下线日期存在之前就发送——RFC 明确表示它不改变任何行为。
Deprecation - 日期确定后添加 头(RFC 8594——信息性文档,不是标准轨道;引用时请相应标注)。它在已弃用版本的响应中携带该版本停止响应的精确时间戳。
Sunset - 附加 (RFC 8288),让工具无需解析正文就能找到替代版本,并在 OpenAPI 文档中标记
Link: rel="successor-version",使文档和代码生成能呈现该信息。deprecated: true - 绝不要只依赖 Header。 大多数 API 不使用这些 Header,而且“原因大多是文化层面的”——这意味着大多数集成方的工具也不会展示它们。请将 Header 与至少一个能触达人类的渠道配合:变更日志、开发者控制台横幅、针对仍在调用弃用版本的账户发送邮件,或在响应中加入警告(Slack 在 API 响应载荷中嵌入弃用通知——这恰好能触达仍然受影响的调用方,比大规模邮件推广更便宜)。
- 负责迁移,而不仅仅是发布公告。 “Churn Rule(流失规则)”:如果你拥有被弃用的东西,你就有责任迁移你的消费者——提供包含具体步骤的迁移指南、在可行时提供工具,并提供支持渠道。发布一个下线公告然后让集成方自己想办法,是一个被明确命名的失败模式,而不是中间路线。强制性弃用(硬性截止日期)要求提供迁移支持;建议性弃用(仅警告,消费者按自己的时间线迁移)是默认方式,直到维护成本或安全风险证明有必要强制推动。
弃用沟通与兄弟技能 有重叠——划分如下:本技能负责有计划的、长达数月的版本生命周期沟通;那个技能负责平台当前故障时的 incident 和状态页沟通。
samber/developer-platform-skills@api-status-communication6. Choose the enforcement style at the sunset date
6. 选择下线日期的执行方式
Two real styles exist, and the choice matters more than RFC compliance:
- Hard cutoff (default recommendation): the retired version returns a clear, described error - Salesforce's "endpoint has been deactivated" message, Twilio's 404 on its 2008 API, GitHub's . Integrators diagnose and fix a loud failure.
410 Gone - Soft fall-forward: the platform reroutes requests naming a retired version to the oldest still-accessible version (Shopify, documented). Reserve it for cases where silent continuity is genuinely safer than breakage, and document it loudly - Meta's Graph API is the cautionary tale: "Meta does not return an error. It quietly reroutes your calls to an older, still-usable version, and your app keeps running while its behaviour changes underneath you." Documented is not the same as noticed; Meta's fall-forward is in its docs and still generates persistent complaints about silent behavior drift.
- Either way, follow the sunset date with a grace period returning rather than dropping connections - callers get a diagnosable error, not silence.
410 Gone - Enforcement style can differ per surface of one platform: Meta's Marketing API hard-fails expired calls on a ~90-day window while the core Graph API falls forward for two years - audience sophistication justifies the split. If you split, write both styles into the policy.
存在两种真实的执行方式,这个选择比 RFC 合规性更重要:
- 硬性切断(默认推荐): 已退役版本返回一个清晰、有明确说明的报错——Salesforce 的“endpoint has been deactivated”消息、Twilio 对其 2008 年 API 返回 404、GitHub 返回 。集成方可以诊断并修复一个明显的失败。
410 Gone - 软性回退: 平台将指向已退役版本的请求重新路由到最旧但仍可访问的版本(Shopify,有文档记录)。请将其保留给“静默连续性确实比中断更安全”的场景,并大声地记录在案——Meta 的 Graph API 是警示故事:“Meta 不会返回错误。它会悄悄把你的调用重路由到一个更旧但仍可用的版本,你的应用继续运行,而它的行为在你脚下悄然改变。” 有文档记录不等于被注意到;Meta 的回退行为写在其文档中,却仍然持续引发关于静默行为漂移的抱怨。
- 无论哪种方式,在下线日期之后都要有一段宽限期,返回 ,而不是直接断开连接——调用方得到的是一个可诊断的报错,而不是沉默。
410 Gone - 同一个平台的不同接口面可以采用不同的执行方式:Meta 的 Marketing API 在约 90 天窗口内对过期调用直接硬性失败,而核心 Graph API 则回退两年——受众的成熟度证明了这种区分的合理性。如果你要区分,请把两种方式都写进策略。
7. Stand up governance proportionate to blast radius
7. 按爆炸半径建立相应治理机制
Governance maturity, not scheme sophistication, is what separates platforms that retire versions cleanly from those that generate incident postmortems. Three stages - grow into them, don't adopt the last one on day one:
- Floor: automated diff/linter in CI plus named-engineer sign-off. Wire an OpenAPI diff or linter (Spectral, Zally, oasdiff) to flag breaking changes mechanically; a staff engineer approves anything flagged. This is the minimum, not a mature end-state.
- Review board with a written charter once the surface outgrows one reviewer: Azure's Breaking Change Review Board blocks spec merges on approval and - the detail worth copying - its typical output is "alternative designs that preserve behavior for current customers," not an approve/reject verdict. The Fuchsia API Council charter is a public template to adapt rather than drafting from scratch.
- Full guideline program at platform scale: Google's AIPs (AIP-180/181 govern compatibility and stability), an enforcing linter, human readability certification, and design review. Google's own "API Governance at Scale" paper records why the layers exist: manual review alone "did not scale well."
The rule that holds at every stage: no breaking change ships without the governance layer's approval. AIP-181 calls an in-place breaking change to a stable API "an extreme course of action" requiring governance-team approval - informal ask-around processes are exactly what the staged models above exist to replace.
区分“能干净退役版本”的平台和“不断产生事故复盘”的平台的,是治理成熟度,而不是方案的精巧程度。三个阶段——逐步成长,不要第一天就采用最后一个阶段:
- 底线:CI 中的自动化 diff/linter + 指定工程师签字。 接入 OpenAPI diff 或 linter(Spectral、Zally、oasdiff),机械地标记破坏性变更;由一名高级工程师批准所有被标记的变更。这是最低要求,不是成熟终点。
- 当接口面超过单个评审者能覆盖的范围时,建立有书面章程的评审委员会: Azure 的 Breaking Change Review Board 在未获批准时阻止 spec 合并,并且——值得抄下来的细节——它的典型输出是“为现有客户保留行为的替代设计”,而不是简单的通过/拒绝裁决。Fuchsia API Council 章程是一个公开模板,可以直接改编,而不是从零起草。
- 平台规模上的完整指南体系: Google 的 AIP(AIP-180/181 管理兼容性和稳定性)、一个强制执行检查的 linter、人工可读性认证,以及设计评审。Google 自己的《API Governance at Scale》论文记录了这些层级存在的原因:仅靠人工评审“无法很好扩展”。
每个阶段都成立的规则:没有治理层的批准,任何破坏性变更都不得发布。 AIP-181 将直接修改一个稳定 API 的破坏性变更称为“极端行动”,需要治理团队批准——非正式的“到处问一圈”流程正是上述分级模型要取代的东西。
8. Brainstorm candidates, then draft and validate the policy
8. 头脑风暴候选策略,然后起草并验证策略
Do not jump from the interview to a finished document.
- Present 2-3 candidate policies - coherent bundles of scheme + windows + enforcement + governance, not per-axis à la carte - each with its trade-offs stated, and name your recommendation with the reason. Example split: a Shopify-shaped calendar policy (quarterly versions, 12-month support, fall-forward), a GitHub-shaped conservative policy (date versions, 24-month notice, hard 410), a Stripe-shaped pinning policy if the promotion conditions from step 3 hold.
- On the user's pick, draft the policy document section by section against references/policy-document-template.md. Validate each section with the user before writing the next:
- Scheme
- Definition
- Windows
- Communication
- Enforcement
- Governance
- Exceptions
- Gate finalization on explicit approval of the assembled document.
不要从访谈直接跳到一份成品文档。
- 提供 2-3 个候选策略——每个都是“方案 + 窗口 + 执行 + 治理”的连贯组合,而不是按维度拆分点菜;说明每个方案的权衡,并给出你的推荐及理由。示例切分:Shopify 式日历策略(季度版本、12 个月支持、回退执行)、GitHub 式保守策略(日期版本、24 个月通知、硬性 410)、Stripe 式固定版本策略(如果步骤 3 中的提升条件成立)。
- 根据用户的选择,对照 references/policy-document-template.md 逐节起草策略文档。在写下一节之前,先与用户确认每一节:
- 方案
- 定义
- 窗口
- 沟通
- 执行
- 治理
- 例外
- 最终定稿必须基于用户对完整文档的明确批准。
Failure modes
失败模式
Each of these is a documented, named incident pattern - check the draft policy against all of them:
- A version simultaneously deprecated and default. Discord served v6 as both for ~1.5 years, then jumped the default to v10 skipping three versions - the deprecation label loses all credibility while you keep handing the deprecated version to new integrators.
- Unversioned access allowed - see step 3; removing it later is an unplanned breaking change.
- Business-motivated changes skipping the process. Twitter/X gave ~7 days' notice for its 2023 paid-API cutoff - two orders of magnitude off every benchmark, executed after developer relations was laid off - and broke integrators who were willing to pay. Governance discipline matters most exactly when the motive isn't technical, because that is when skipping it is most tempting.
- Moving sunset dates. Repeated slippage (Twilio: three extensions) teaches integrators to ignore your dates.
- Headers-only communication - signals nobody's tooling reads, satisfying the RFC and reaching no one.
- Announce-and-walk-away - the Churn Rule violation: no migration guide, no tooling, no targeted outreach.
- Undocumented fall-forward - functionally indistinguishable from a bug report waiting to happen.
- An undefined clock - "supported for 2 years" without stating when the 2 years start (Meta's starts at the next release).
- Treating GraphQL as removal. It is a documentation signal; removal requires usage telemetry showing zero real consumers, field by field.
@deprecated
以下每一种都是已被记录、被命名的事故模式——请用它们逐一检查策略草稿:
- 一个版本同时处于弃用状态且仍是默认版本。 Discord 将 v6 同时作为两者约 1.5 年,然后把默认版本直接跳到 v10,跳过了三个版本——当你持续把已弃用版本交给新集成方时,“弃用”标签就失去所有可信度。
- 允许未版本化访问——见步骤 3;之后移除它就是一个计划外的破坏性变更。
- 出于商业动机的变更绕过流程。 Twitter/X 为其 2023 年付费 API 切断只给了约 7 天通知——比所有基准低两个数量级,并且是在裁掉开发者关系团队后执行的——它破坏了那些本来愿意付费的集成方。治理纪律恰恰在动机不是技术性的时候最为重要,因为那时最想跳过流程。
- 不断推迟下线日期。 反复延期(Twilio:三次延长)会教会集成方忽略你的日期。
- 只靠 Header 沟通——发出的信号没有人的工具会读取,满足了 RFC,却触达不到任何人。
- 公告后走开——违反 Churn Rule:没有迁移指南、没有工具、没有定向触达。
- 没有文档记录的回退——在功能上等同于一个等待发生的 bug 报告。
- 计时起点不明确——只说“支持两年”,却不说明两年从何时起算(Meta 是从_下一个_版本发布时开始)。
- 把 GraphQL 的 当作移除。 它只是一个文档信号;移除需要按字段、由显示零真实消费者的用量遥测来支持。
@deprecated
Measurement
度量
- Breaking-change frequency from telemetry: more than ~2/year signals the scheme or the compatibility discipline is wrong (step 2's escape hatch). This is the policy's standing health metric.
- Migration completion by usage, never by calendar: a version is removable when telemetry shows zero (or a consciously-accepted residue of) real traffic - an announcement date passing proves nothing about who migrated.
- Deprecated-version traffic share trending down after each communication wave tells you which channels actually reach your integrators; a flat line after a mass email is evidence the channel failed, before the sunset date makes it an incident.
- Date integrity: count slipped sunset dates. The sourced evidence says slippage is common (Salesforce, Twilio) - the target is zero anyway, because each slip devalues every future date. No industry threshold exists for the traffic metrics; baseline them on your first deprecation cycle and improve against that.
- 来自遥测的破坏性变更频率: 每年超过约 2 次,说明方案或兼容性纪律有问题(步骤 2 的逃生舱口)。这是本策略的长期健康指标。
- 迁移完成度看用量,绝不看日历: 当遥测显示真实流量为零(或被有意识接受的残留)时,版本才可以移除——一个公告日期过去并不能证明谁迁移了。
- 已弃用版本的流量占比 在每轮沟通后呈下降趋势,告诉你哪些渠道真正触达了集成方;群发邮件后曲线持平,就是该渠道失败的证据——别等到下线日期把它变成事故。
- 日期完整性: 统计被推迟的下线日期。来自来源的证据表明延期很常见(Salesforce、Twilio)——但目标仍然是零,因为每一次延期都会贬低未来所有日期的价值。行业对流量指标没有统一阈值;请在第一个弃用周期中建立基线,并在此基础上改进。
Invocation examples
调用示例
- "Write a versioning and deprecation policy for our public REST API - we've never had one and a breaking change is queued."
- "We're retiring /v1 next year - design the sunset timeline, communication plan, and what happens to callers who never migrate."
- "Should our GraphQL API be versioned like our REST API, or evolve continuously? Draft the policy either way."
- “为我们的公开 REST API 编写一份版本管理与弃用策略——我们从来没有过,而且已经有一个破坏性变更排队等着。”
- “我们明年要退役 /v1——设计下线时间线、沟通计划,以及那些从不迁移的调用方会怎样。”
- “我们的 GraphQL API 应该像 REST API 一样做版本管理,还是持续演进?无论哪种,请起草策略。”
References
参考资料
- references/versioning-case-studies.md - Stripe's gate/transformer mechanics, the conservative platforms (Shopify, LinkedIn, AWS, Azure, Google Maps, PayPal/Braintree), and the cautionary tales (Meta, Discord, Twitter/X).
- - error codes are API contract too; retiring one runs through this skill's deprecation machinery.
samber/developer-platform-skills@api-error-design - - event schema evolution follows the same breaking-change definition and notice discipline defined here.
samber/developer-platform-skills@webhook-platform-design - - the schema-side design that makes step 1's continuous-evolution path possible (additive-friendly payloads, result unions,
samber/developer-platform-skills@public-graphql-api-designfield rollover); this skill sets the notice discipline that removal then follows.@deprecated - - bulk file/lake exports evolve their schema under a separate additive-only contract, not this skill's API deprecation machinery.
samber/developer-platform-skills@bulk-data-sharing-design
- references/versioning-case-studies.md —— Stripe 的门禁/转换器机制、保守型平台(Shopify、LinkedIn、AWS、Azure、Google Maps、PayPal/Braintree),以及警示故事(Meta、Discord、Twitter/X)。
- —— 错误码也是 API 契约的一部分;退役一个错误码也要经过本技能的弃用机制。
samber/developer-platform-skills@api-error-design - —— 事件 schema 的演进遵循本文定义的破坏性变更定义和通知纪律。
samber/developer-platform-skills@webhook-platform-design - —— 使步骤 1 的持续演进路径成为可能的 schema 侧设计(利于增量的载荷、结果联合、
samber/developer-platform-skills@public-graphql-api-design字段迁移);本技能设定移除时所遵循的通知纪律。@deprecated - —— 批量文件/数据湖导出的 schema 在独立的纯增量契约下演进,不属于本技能的 API 弃用机制。
samber/developer-platform-skills@bulk-data-sharing-design