The API documentation strategy decision record: why the spec-generation model you chose determines your breaking change detection gap and your integration partner notification failure mode
The spec-generation model — code-first from annotations, contract-first from a hand-authored spec, or hand-maintained prose and examples — the breaking change classification policy, and the consumer notification mechanism are API documentation decisions that are almost never made explicitly — they emerge from an OpenAPI annotation added to the first endpoint, a documentation site provisioned to avoid a blank reference page, and a CI pipeline that deploys code without diffing the spec against the previous version. Three failure patterns: the fintech SaaS that refactored two response fields into a nested object and regenerated its spec without a diff gate in CI, causing four enterprise reconciliation pipelines to discover the breaking change between 18 hours and 30 days after deployment; the developer tooling platform that marked an endpoint deprecated: true in the OpenAPI spec and removed it 8 months later, breaking 9 of 60 integrations because the documentation banner was the only notification and active users do not read documentation for endpoints they already understand; and the B2B SaaS that hand-maintained its API documentation and fixed a field type from Unix epoch integer to ISO 8601 string, where three integration partners whose parsers broke received no notification because a documentation update is as invisible to existing consumers as a code deploy.
A 36-person fintech SaaS company built a payment reconciliation platform — expense categorization, transaction matching, and financial reporting for finance teams at mid-market companies. The platform's API was central to their enterprise offering: 23 enterprise customers had built automated reconciliation pipelines that called the API nightly or monthly to import transaction records into their ERP systems, accounting software, or internal finance databases. The engineering team used FastAPI with Pydantic models; the OpenAPI spec was generated automatically from the Python type annotations on every request and response model at build time. The documentation site at /docs was a Redoc-hosted page that was updated at every deploy. The team considered the documentation "always current" — and it was, in the sense that it accurately described what the code did at any given moment.
What the team had not decided was whether the OpenAPI spec was documentation — a human-readable description of what the API currently does, accurate as of the last deploy — or a contract — a versioned artifact that consumers can depend on across deploys, and that any breaking change to requires an explicit, communicated, versioned upgrade path with advance notice. The distinction had never been discussed. The practical default was documentation: the spec described the code, and when the code changed, the spec changed.
In month fourteen, a senior backend engineer addressed a long-standing bug in the transaction summary endpoint. The bug: two top-level response fields — amount (the gross transaction value in cents) and net_amount (the gross value minus platform fees) — were calculated independently in two places in the codebase and could diverge by up to $0.02 on transactions with complex fee schedules. The fix consolidated both calculations: the engineer replaced the two top-level fields with a single transaction_value object containing three subfields — gross, net, and fee_total — computed in a single pass through a consistent formula. The Pydantic response model was updated. All 47 existing API tests passed — the tests had been updated to the new shape as part of the same PR. The regenerated OpenAPI spec correctly described the new response. The CI pipeline ran: tests passed, spec regenerated, deploy succeeded. No warning was issued. No notification was triggered.
The CI pipeline had no step that diffed the new spec against the previous spec and failed the build on a detected breaking change — the removal of amount and net_amount, the addition of transaction_value.gross, transaction_value.net, and transaction_value.fee_total. The deploy went to production at 2:17 PM on a Wednesday. No changelog entry described the schema change. No email was sent to integration customers. The endpoint URL was unchanged: /v1/transactions/{id}. No version bump in the URL or in the API-Version header.
Four enterprise customers had automated reconciliation pipelines that accessed the transaction summary endpoint. The first ran a daily pipeline at 11:00 PM UTC that imported the previous day's transactions into a NetSuite instance. The pipeline's field mapping expected response['amount'] and response['net_amount']; both were now absent. The pipeline raised a KeyError on the first invocation after the deploy. An error alert fired at 11:03 PM. The first customer filed a support ticket at 11:47 PM — 9.5 hours after the deploy.
The second customer ran a weekly batch job on Sunday mornings at 6:00 AM UTC that reconciled the prior week's transactions and posted journal entries to QuickBooks Online. The job ran 4.5 days after the deploy. The KeyError on amount caused the job to abort at the first transaction and raise an unhandled exception. The second customer discovered the break Monday morning when the scheduled reconciliation email did not arrive. They filed a support ticket 4 days, 21 hours after the deploy.
The third and fourth customers ran monthly reconciliation pipelines on the last business day of the month. The deploy happened on the 14th; the last business day of the month was 17 days later. Both monthly pipelines ran and failed. One failed immediately with a KeyError; the other had written its field mapping as response.get('amount', 0) — using a default value of 0 for absent fields to handle occasional API errors gracefully. The .get() call returned 0 for every transaction in the import; all transactions were imported with amount = 0. The pipeline completed without error. The customer did not detect the issue until a bank statement reconciliation 13 days later revealed that the previous month's imported transactions all showed $0. The data required a full re-import and a correction run in their accounting system for all journal entries from the prior month.
The incident review identified two root causes. First: the team had never decided whether the spec was documentation or a contract — the default was documentation, and documentation is expected to change when the code changes. Second: the CI pipeline had no mechanism to detect and block a breaking spec change. The breaking change was a code-quality improvement — the consolidated calculation was genuinely better than the two-field approach — but its delivery mechanism treated it as invisible to consumers. The 30-day gap for the monthly pipeline customer was not an accident of timing: any team that runs monthly reconciliation and builds against an undiffed, unversioned API is structurally exposed to a 30-day silent data corruption window on any breaking change to any field their pipeline reads.
A 41-person developer tooling company built a static analysis and dependency auditing platform — vulnerability scanning, license compliance checking, and SBOM generation for engineering teams. The platform's REST API was the primary integration surface: 60 registered integration customers pulled analysis results into their own security dashboards, CI reporting pipelines, and compliance reporting tools. The team had invested in their API documentation: a well-structured OpenAPI spec maintained in the same repository as the code, published via a Redoc-hosted page, versioned with a v1 prefix on all endpoints.
In month twelve, the team decided to redesign the export functionality. The original /v1/builds/export endpoint returned a single CSV dump of all analysis results for a build. It did not scale to their largest customers, who had build histories with 50,000+ items. The new design was a paginated /v1/builds/{id}/artifacts endpoint with cursor-based pagination, filtering by severity and date range, and streaming support for large exports. The new endpoint was better in every dimension: faster, filterable, and compatible with large build histories.
To deprecate the old endpoint, the team added deprecated: true to the /builds/export entry in the OpenAPI spec. In the Redoc documentation UI, this rendered a yellow "Deprecated" banner at the top of the endpoint's documentation page. The team reviewed the documentation page, saw the yellow banner, and considered the deprecation communicated. No email was sent to the 60 registered integration customers. No Deprecation header was added to the endpoint's responses (RFC 8594's mechanism for carrying the deprecation date in the response itself). No Sunset header was added specifying the removal date. The changelog entry for the release said "Added /v1/builds/{id}/artifacts endpoint with pagination and filtering support."
The team planned to remove /builds/export after a 6-month deprecation window — a standard window for enterprise integrations. At month eighteen (6 months after the deprecation), the platform's access logs showed /builds/export receiving requests from 23 of the 60 registered integration customers on at least a monthly basis. The team deferred removal by 2 months: "active usage means they haven't migrated; we should wait." At month twenty, the team removed the endpoint. The last 14 days of access logs showed 9 integration customers had called it within that window.
All 9 broke simultaneously. The failure modes varied by integration design. Three were CI pipelines that ran on every build and called /builds/export to post a summary to an internal Slack channel; they failed with 404 errors on the next commit, failing the build. Four were scheduled weekly or monthly report generators that called the endpoint as part of their data collection; they failed silently on their next scheduled run, producing empty reports or unhandled exceptions depending on how they handled HTTP errors. Two were on-demand export tools used by compliance teams; they were not discovered broken until a compliance officer requested an export 11 days after the removal.
Support ticket volume from 9 simultaneous integration failures required 3 engineers for 4 business days to resolve. Each customer needed: confirmation that the endpoint was removed intentionally (not a production incident), the name and URL of the replacement endpoint, a migration example showing the authentication change (the new endpoint required an Authorization: Bearer header whereas the old endpoint had used a query-parameter API key — a migration that required code changes beyond just updating the URL), and direct assistance from a support engineer for the two customers whose integrations were written by contractors who were no longer available. Five of the nine customers said they had been unaware the endpoint was deprecated. Two said they had seen the documentation banner months earlier but had intended to migrate "when we have time." Two had never revisited the documentation page after their initial integration.
The root cause was not the 8-month deprecation window — that was a reasonable period. The root cause was the mismatch between the audience for the deprecation notification and the audience who needed to receive it. The deprecated: true flag communicated the deprecation to people who would visit the documentation in the future, evaluating whether to build on the endpoint. Every integration customer who called the endpoint regularly was, by definition, not visiting the documentation to evaluate whether to use it — they had already made that decision months or years ago. The yellow banner was addressed to an audience that did not include the 9 customers who needed to receive the message.
A 47-person B2B SaaS company built a project intelligence platform — engineering metrics, code change analytics, and team health reporting for engineering leaders at growth-stage companies. The platform exposed a REST API for data export, and had maintained its API documentation by hand since launch: a Markdown-based reference site with example request and response payloads written by the engineer who implemented each endpoint and updated by the engineer who later modified it. The team valued hand-written documentation for its explanatory quality — hand-written examples could include annotations, caveats, and context that generated examples could not. They accepted the tradeoff that hand-written examples could drift from the implementation.
In month sixteen, a customer filed a bug report: the created_at field in the activity events endpoint was returning Unix epoch timestamps (integers, e.g. 1714915200) rather than ISO 8601 datetime strings. Every other date field in the API returned ISO 8601 strings. The original engineer had used epoch integers because they were "easier to sort in the database query"; the inconsistency had not surfaced until a customer tried to join the activity events response with another API's response and encountered a type mismatch. The team investigated and agreed the original behavior was a bug: the field should have been ISO 8601 from the start, consistent with every other date field in the API. They fixed it: updated the serialization layer to return ISO 8601 strings, updated the test assertions to expect strings, updated the hand-maintained documentation to show the correct field type and a corrected example, and deployed.
The documentation update was accurate. The spec's created_at field type was changed from integer to string (date-time format). The example value was updated from "created_at": 1714915200 to "created_at": "2024-05-05T08:00:00Z". From the team's perspective, the documentation was correct and the bug was fixed. No consumer notification was sent — the team classified the change as a bug fix, not a breaking change, on the reasoning that the old behavior was incorrect and the new behavior was what consumers should have expected given the documented API conventions for date fields.
Three integration partners had built parsers against the original epoch format — the format that had been correctly documented in the spec and examples at the time they built their integrations. The first integration partner had written a Python pipeline that processed activity events nightly: event_date = datetime.fromtimestamp(event['created_at']). After the fix, event['created_at'] was the string "2024-05-05T08:00:00Z". datetime.fromtimestamp("2024-05-05T08:00:00Z") raises a TypeError: an integer is required. The pipeline aborted on the first event of the next nightly run. An alert fired. The customer filed a support ticket at 6:23 AM — within 8 hours of the deploy.
The second integration partner had written a TypeScript webhook consumer that enriched a reporting dashboard. Their parser used new Date(event.created_at * 1000) — multiplying the epoch integer by 1000 to convert seconds to milliseconds before constructing a JavaScript Date object. After the fix, event.created_at was the string "2024-05-05T08:00:00Z". In JavaScript, "2024-05-05T08:00:00Z" * 1000 evaluates to NaN (string × integer = NaN). new Date(NaN) produces an Invalid Date object. The Invalid Date did not throw an exception — it was a valid JavaScript object. The integration's date-sorting logic, which called event.timestamp.getTime(), returned NaN for all events processed after the fix. The sort comparator treated NaN as less than any valid timestamp, placing all new events at the beginning of the sorted list rather than at the end. The dashboard displayed new activity events as if they had occurred before all historical events. This was visually subtle — the customer saw their oldest events at the top of the list, which could be explained as a sort order inversion. They discovered the root cause 11 days later while debugging an unrelated report discrepancy.
The third integration partner had written a Go data pipeline that imported activity events into a data warehouse. Their struct used CreatedAt int64 with a json tag on the field. After the fix, the JSON response contained "created_at": "2024-05-05T08:00:00Z" — a string value for a field typed as int64 in the Go struct. Go's json.Unmarshal silently sets the integer field to its zero value (0) when the JSON value is a string that cannot be parsed as a number. The pipeline produced no error. Every event imported after the fix had CreatedAt = 0, which time.Unix(0, 0) converted to January 1, 1970, 00:00:00 UTC. The pipeline's deduplication logic used event_id + formatted_date as a composite key; since no existing record had a date of 1970-01-01, all events with the zero timestamp were inserted as new records. The data warehouse accumulated a growing table of events timestamped 1970-01-01. The third integration partner discovered the issue 23 days after the fix during a routine data audit that showed an unexpected spike in 1970-dated activity records.
The incident review identified a classification question that had not been decided: whether a change to a previously incorrect behavior constitutes a breaking change. The team's intuition was that fixing a bug is not a breaking change — the old behavior was wrong, the new behavior is correct, and consumers should update their code to match the correct behavior. The consumers' intuition was that any change to the observable behavior of an API they have built against is a breaking change, regardless of whether the previous behavior was intended. Both intuitions are coherent. The API documentation strategy decision record must specify which definition the team uses — because the definition determines whether bug-fix deployments trigger the consumer notification workflow or bypass it. A team that never treats behavior changes as breaking changes, even when those changes are to documented incorrect behavior, will eventually break a consumer who had built against the documented incorrect behavior and receive no warning.
Structural properties set by the API documentation strategy decision
Three structural properties are determined when a team decides — or fails to explicitly decide — how to manage API documentation: what the spec-generation model determines about the breaking change detection gap when the CI pipeline does not diff the spec, what the deprecation notification model determines about the active-consumer blind spot when the notification channel is the documentation itself, and what the spec-implementation parity model determines about the silent-failure example divergence mode when documentation updates are invisible to consumers who have already integrated. None of these properties are labeled as decisions in the conversations that produce them. The spec-generation model emerges from the first framework choice that auto-generates an OpenAPI spec — the default for FastAPI, Django Ninja, Spring Boot, and NestJS. The deprecation notification model emerges from a documentation convention adopted without considering which audience the convention actually reaches. The parity model emerges from a documentation maintenance practice that treats accuracy as a best-effort property rather than an enforced constraint.
Property 1: The spec-generation model and the breaking change detection gap. A code-first spec-generation model that regenerates the spec at build time without diffing against the previous version creates a breaking change detection gap that spans from the moment the breaking change is deployed to the moment each integration consumer discovers it through their own error monitoring. The gap's width is not determined by the team's deployment process — it is determined by each consumer's invocation cadence. A consumer with daily invocations discovers the break within 24 hours; a consumer with monthly invocations may not discover the break for 30 days. During the gap, the consumer's integration may be silently failing (raising unhandled exceptions that abort the pipeline), silently corrupting data (using default values for absent fields, producing zero-value records), or silently producing incorrect output (mis-sorting, miscalculating, mis-filtering). The first two failure modes surface through error monitoring; the third may only surface through a data audit. The detection gap is not a property of the consumer's monitoring practice — it is a structural property of the team's deployment process. A CI pipeline that does not diff the spec treats a breaking schema change as indistinguishable from a non-breaking code change, and deploys both with the same absence of consumer notification. The prevention is a spec diff gate in CI that classifies every spec change as breaking or non-breaking and blocks breaking changes from deploying without an explicit version increment — making it structurally impossible to deploy a breaking change without triggering the consumer notification workflow. Connect this property to the API versioning strategy decision record: the version increment that the spec diff gate requires when a breaking change is detected is meaningful only if the versioning strategy specifies what a version increment communicates to consumers and how long the previous version remains supported; a breaking change that requires a version bump is only an improvement over an unversioned breaking change if consumers know to check for version bumps and know what migration is required.
Property 2: The deprecation notification gap and the active-consumer blind spot. The deprecation notification gap is the structural failure of communicating a deprecation exclusively through the documentation — a channel that is consumed by future evaluators of the API, not by current users of the endpoint. The active-consumer blind spot is the gap between the audience the documentation-based deprecation actually reaches and the audience that needs to receive the deprecation notification. Integration customers who are actively calling an endpoint have no reason to revisit the documentation for that endpoint: their code works, they understand the interface, and the documentation holds no new information for them. The deprecated: true flag in the spec, the yellow banner in Redoc, the note in the changelog — none of these mechanisms have a delivery path to an integration customer who wrote their integration 14 months ago and has not opened the documentation since. The active-consumer blind spot grows with the age of the integration: an endpoint that has been in production for 4 years has more integrations built by engineers who have since left the team, written by contractors who are no longer available, and maintained by inheritors who have no idea the integration exists beyond the fact that the reporting pipeline runs every Sunday. For these integrations, the deprecation notification must be a push mechanism — an email, an in-platform alert, or a webhook — delivered to the account that registered the API key associated with the calls, at the moment the deprecation is declared. The push notification must include the sunset date and the migration target in actionable terms, not a link to the documentation. The monitoring mechanism that makes this possible is an API consumer registry: a table of which accounts are calling which endpoints, maintained from the access logs, that enables targeted deprecation notifications rather than broadcast announcements that are easy to miss. Connect this property to the API deprecation strategy decision record: the deprecation timeline — the window between the deprecation declaration and the endpoint removal — is a secondary concern compared to the notification mechanism; a 12-month deprecation window communicated only through the documentation is less effective than a 3-month window communicated via direct email to every consumer in the registry, because the window length determines how much time consumers have to migrate and the notification mechanism determines whether they know they need to.
Property 3: The spec-implementation parity surface and the silent-failure example divergence mode. The spec-implementation parity surface is the set of all claims in the API documentation — field names, types, formats, example values, error codes, authentication requirements — that can be incorrect while the API itself continues to function. For code-first documentation, this surface is small: field names and types are derived from the code and cannot diverge. For hand-maintained documentation, this surface encompasses every field type, every example value, and every described behavior in the reference documentation. The silent-failure variant of the example divergence mode is the most consequential: a consumer that builds a parser against the documented field type will receive a parser behavior that is determined by their language's handling of the unexpected type, not by a well-defined error. JavaScript's implicit type coercion converts a string where an integer was expected into NaN, which propagates silently through arithmetic and comparisons. Go's json.Unmarshal sets an integer field to zero when the JSON value is a string, silently zeroing all timestamps in a batch import. Python's datetime.fromtimestamp raises a TypeError that aborts the pipeline loudly. The failure mode depends on the consumer's language and parser implementation, not on the documentation error — and the silent variants (JavaScript NaN, Go zero-value) are the most damaging because they produce corrupted data without triggering any alert. The spec-implementation parity surface cannot be maintained by documentation discipline alone at team velocity — it requires automated verification: a contract test suite that calls the live API and asserts on field types, formats, and shapes using the documented spec as the assertion source. When the contract test suite runs on every deploy, a deploy that changes the response shape will either fail the contract tests (if the tests are asserting on the changed fields) or pass while adding a new field to the monitored surface for the next test update. Connect this property to the API contract testing decision record: the contract tests that verify documentation accuracy are distinct from consumer-driven contract tests that verify consumer compatibility; documentation accuracy tests assert that the implementation matches the documentation; consumer-driven contract tests assert that the implementation matches what a specific consumer's code expects; both types are needed, and both should be in CI, because a documentation accuracy failure is a proxy for a future consumer compatibility failure — the consumer who built against the documented type will break when the implementation diverges from it.
The API documentation strategy decision ADR: five sections
Section 1: Spec-generation model selection and the contract-versus-documentation decision. Begin the API documentation strategy decision record by specifying the spec-generation model and explicitly deciding whether the OpenAPI spec is documentation or a contract. The three generation models have different properties: code-first (spec derived from code annotations) guarantees that the spec accurately describes the current implementation but makes the spec a byproduct of the code rather than a governed artifact; contract-first (spec authored independently, code generated from or validated against the spec) makes the spec the governing artifact but requires tooling and process discipline to prevent the implementation from drifting from the spec; hand-maintained (spec and examples written and updated manually) enables documentation quality that neither generated approach can match but creates a continuous parity debt that grows with code velocity. For any API with external consumers, document the explicit decision: "this spec is documentation — it describes the current API accurately but is not versioned independently, and changes to it may reflect breaking changes delivered without advance notice" or "this spec is a contract — breaking changes require a version increment and a consumer notification process before deployment." Neither is wrong, but the default — treating the spec as documentation because it was generated from the code — means the team has made the first choice without knowing they made it, and without the consumer-communication process that the choice requires to be non-damaging. Specify the enforcement mechanism for the chosen model: for contract-first, the enforcement is a spec validation step in CI that fails the build when the implementation diverges from the spec (using tools like dredd, schemathesis, or a custom middleware that validates every request and response against the spec at the routing layer); for code-first contracts, the enforcement is a spec diff gate (described in section 2); for hand-maintained documentation, the enforcement is a contract test suite (described in section 5). Connect to the API versioning strategy decision record: the choice between documentation and contract is upstream of the versioning strategy; a contract model requires a versioning strategy that specifies what a version increment communicates, how long previous versions are supported, and how migration guides are maintained; a documentation model does not require versioning at the spec level, but pairs with a versioned URL scheme (/v1/, /v2/) so that consumers can anchor their integration to a stable URL prefix even if the spec changes within the version.
Section 2: Breaking change classification and the spec diff enforcement model. Specify the breaking change classification policy — the set of spec changes that require a version increment and a consumer notification process — and the enforcement mechanism that makes it impossible to deploy a breaking change without triggering that process. The breaking change categories for a REST API: (a) a field removed from a response schema; (b) a field renamed in a request or response schema; (c) a field's type changed (integer to string, object to array, required to optional in a way that changes parser behavior); (d) a required field added to a request schema; (e) an endpoint removed or its URL path changed; (f) an HTTP method changed for an existing path; (g) an authentication scheme changed or a new authentication requirement added; (h) an enum value removed from a field that consumers may be switching on. Non-breaking changes: adding optional fields to a response, adding optional parameters to a request, adding new endpoints, adding enum values (consumers should use a default/unknown case for unknown enum values, making additions non-breaking), reducing constraints (making a required field optional). The enforcement model is a spec diff tool in CI: a job that generates the new spec, diffs it against the spec committed to the repository at the HEAD of the main branch, classifies each change as breaking or non-breaking, and fails the build if any breaking change is detected without a version increment in the same commit. The exemption path: if a breaking change is intentional, the PR must include both the spec change, a version increment in the spec's info.version field, and a MIGRATION.md entry describing the change and the migration path. The diff gate passes when the version has changed and the migration guide exists. Archive the diff report as a CI artifact — the diff report for each version increment is the input to the consumer notification email, listing every breaking change by field path. Specify the classification for bug-fix behavior changes: a change to previously incorrect behavior is a breaking change if the previous behavior was documented, regardless of whether the old behavior was intended. Connect to the API contract testing decision record: the spec diff gate catches breaking changes at the spec level; contract tests catch divergence between the spec and the implementation; both are needed, because a spec diff gate that approves a non-breaking spec change does not detect whether the implementation matches the updated spec — a contract test catches an implementation that was deployed with a schema update that was marked non-breaking but was actually implemented incorrectly.
Section 3: Consumer registry maintenance and the active-consumer notification mechanism. Specify the consumer registry — a maintained record of which API accounts are calling which endpoints — and the outbound notification process that uses the registry to deliver deprecation and breaking change notifications to every affected consumer at the time the change is declared. The consumer registry is built from the access logs: for each API key in use, record the set of endpoints it has called within the past 90 days, the call frequency, the account or company associated with the key, and a contact address for the account. This registry enables two notification workflows: (1) targeted deprecation notification — when an endpoint is deprecated, query the registry for all API keys that have called the endpoint in the past 90 days, and send a direct email to the associated accounts on the day of the deprecation declaration; and (2) targeted breaking change notification — when a breaking change is approved for a versioned upgrade, query the registry for all API keys that have called the affected endpoints in the past 90 days, and send a direct email with the migration guide before the new version is deployed. The notification email must include: the endpoint or field being changed, the type of change, the deployment date (for breaking changes in a new version), the migration path with a code example showing the old and new patterns, and a reply-to address for questions. Send a reminder 30 days before the sunset date to any account that is still calling a deprecated endpoint as of the reminder date, with a note on the specific call volume to signal that you are tracking their usage. Specify the registry maintenance cadence: rebuild the active endpoint table weekly from the access logs, updating the call frequency for each API key. An endpoint whose traffic from a given API key drops to zero after a deprecation notification is evidence of a successful migration; include this metric in the deprecation lifecycle tracking to understand which notification mechanisms are effective and which require follow-up. Connect to the webhook delivery decision record: the webhook API surface is part of the documentation strategy's scope — webhook payload schemas are API contracts that integration consumers build parsers against, and a change to a webhook's payload shape (adding, removing, or renaming fields) requires the same breaking change classification and consumer notification process as a REST endpoint schema change; the consumer registry must include webhook consumers (identified by the endpoint URLs registered for webhook delivery) alongside REST API consumers.
Section 4: Deprecation lifecycle, sunset timeline, and response header protocol. Specify the deprecation lifecycle from declaration to removal, the sunset timeline, and the response header protocol that communicates the deprecation to consumers through the API response channel rather than through the documentation channel. The response header protocol follows RFC 8594: add a Deprecation header to every response from the deprecated endpoint, with the value being the HTTP date of the deprecation declaration (e.g., Deprecation: Sat, 01 Jan 2026 00:00:00 GMT), and a Sunset header with the HTTP date of the planned removal (e.g., Sunset: Sat, 01 Jul 2026 00:00:00 GMT). The header protocol serves three audiences: API monitoring tools that check response headers and surface warnings for deprecated endpoint usage; integration engineers who debug a request in the browser or via a tool like Postman and see the headers in the response; and API client libraries that check for the Sunset header and log a warning when the current date is within 30 days of the sunset date. Specify the sunset timeline as a minimum, not an exact date: the minimum window between the deprecation declaration and the removal is determined by the consumer population's integration complexity, not by a calendar convention. For a public API with external enterprise consumers, the minimum is 6 months from the first notification email to the removal date — 6 months allows teams with slow procurement and engineering cycles to prioritize the migration. Specify the migration completion gate: do not remove the endpoint until the access logs show that calls from non-migrated consumers have dropped to zero, or until the sunset date has passed and the remaining callers have been personally notified by support. The completion gate prevents the situation where a team announces a removal date, then removes the endpoint while significant traffic is still hitting it, requiring a rollback. Specify the escalation for non-migrating consumers: 30 days before the sunset date, query the consumer registry for API keys still calling the deprecated endpoint; for any account with significant call volume (more than 10 calls per day), send a direct escalation email from a named account manager or support lead rather than a generic notification; offer a 30-minute migration call; log the escalation in the registry. Connect to the API deprecation strategy decision record: the deprecation lifecycle here is the documentation strategy's view of deprecation — the notification process, header protocol, and sunset timeline; the API deprecation strategy decision record covers the policy questions — when to deprecate vs. version, how long to maintain parallel versions, and how to manage the operational cost of supporting deprecated endpoints; both records are needed, referencing each other, to form a complete deprecation governance framework.
Section 5: Spec-implementation parity verification and the example accuracy enforcement model. Specify the automated verification model that ensures the API documentation — whether code-first, contract-first, or hand-maintained — accurately reflects the implementation at all times, with a particular focus on example accuracy, which is the claim most likely to diverge silently. For code-first documentation: the field names and types in the schema are derived from the code and cannot be wrong if the code is correct, but example values in the spec (any field annotated with an example: key) are static strings that do not regenerate. Add a CI step that validates every static example in the spec against its schema definition using a JSON Schema validator: an example value that does not validate against its own schema definition should fail the build. This catches the case where an example was added for a field typed as integer and the field type was later changed to string (date-time)), leaving an integer example for a date-time field. For hand-maintained documentation: specify a contract test suite that calls the live API (against a staging environment or a test fixture) and asserts on field types, formats, and shapes using the documented spec as the assertion source. The contract test for each endpoint should assert: the presence of all documented required fields, the type of each field (string, integer, array, object), the format of string fields that have a documented format (date-time, UUID, URI), and the HTTP status code for both success and documented error cases. The contract tests run on every deploy and serve as a continuous spec-accuracy audit. Specify the update cadence for hand-maintained examples: any PR that changes a response field's name, type, format, or example value must include an update to the corresponding documentation file, enforced by a documentation linter that checks for spec files modified in the same commit as the source files for the affected endpoints. This does not prevent a documentation update from being forgotten — it makes forgetting visible in code review rather than invisible until a consumer builds against the wrong documented type. Specify the example divergence incident classification: a consumer who builds against a documented example and breaks when the implementation is fixed is experiencing a documentation failure, not a consumer implementation failure, regardless of which behavior was "correct" — the documentation was the authoritative contract they built against, and the team is responsible for notifying them when it changes. Connect to the observability strategy decision record: monitoring for documentation divergence requires a specific metric: the rate of consumer-side errors (4xx and 5xx) attributable to field type mismatches or schema validation failures; a spike in consumer-side type errors is the production signal that an undocumented or unnotified behavior change has reached a consumer who built against the previous documentation; the observability strategy must include consumer-error attribution — logging or tracing that identifies which API key is experiencing errors and which fields are failing — so that the API documentation team can identify and proactively contact affected consumers before they file support tickets.
FAQ
When should you use contract-first versus code-first API documentation?
Use contract-first when you have multiple integration consumers, when your API is the primary product interface, or when consumer impact from breaking changes carries significant operational cost. In contract-first, the OpenAPI specification is authored before the code — it is the source of truth from which code generation tools derive server stubs and client SDKs. The key property of this model is that spec changes are deliberate: a code refactor cannot accidentally break the contract because the spec is not derived from the code. Any spec change requires an explicit update to the spec file, which is reviewable, triggerable for notification, and gated on breaking change classification before deployment. Use code-first when your API is internal, when you have few consumers, or when development velocity is more important than integration stability. Code-first generates the spec from code annotations — the spec is always structurally accurate but is a byproduct of the code, not a governed artifact. The critical addition to any code-first approach is a spec diff gate in CI that fails the build when the regenerated spec contains a breaking change without an explicit version increment. Without the diff gate, code-first provides no protection against unintentional breaking changes deployed silently to consumers. Avoid hand-maintained documentation for any API with external consumers or SLAs: hand-maintained examples drift from the implementation at a rate proportional to code velocity and inversely proportional to documentation discipline, and there is no automated mechanism to enforce accuracy at scale.
How do you detect breaking changes in an API spec before they reach consumers?
Install a spec diff tool in CI that compares the generated or updated spec against the previous version on every commit. Standard tools: oasdiff (Go), openapi-diff (Java), breaking-change-detector (Python). Configure the tool to fail the build on: a field removed from a response schema, a field's type changed, a required field added to a request schema, an endpoint removed or its HTTP method changed, an authentication scheme changed, an enum value removed. These are the breaking change categories — changes that can cause a correctly implemented consumer to fail without any change on their end. Non-breaking changes (adding optional response fields, adding endpoints, adding enum values) pass the diff gate without failure. The exemption path: if a breaking change is intentional, the PR must include both the spec change and a version increment; the diff gate passes when the version has changed. This creates a workflow where a breaking change cannot reach production without an explicit version increment, which triggers the consumer notification process. Archive the diff report as a CI artifact: the diff report for each version increment becomes the input to the migration guide and consumer notification email, listing every breaking change by field path. For hand-maintained documentation, add a contract test suite that calls the staging API and asserts on field types from the spec — a test that asserts isinstance(response['created_at'], str) will catch a type change before deployment.
How do you communicate API deprecations to integration partners?
Deprecation communication requires three parallel mechanisms: response headers on the deprecated endpoint, direct outbound notification to registered consumers, and a documented migration guide with a concrete sunset date. The response headers follow RFC 8594: add a Deprecation header to every response from the deprecated endpoint (the ISO 8601 or HTTP date of the deprecation declaration) and a Sunset header specifying the removal date. These headers are visible in every API response and are checked by API monitoring tools and modern API client libraries. The direct outbound notification closes the active-consumer blind spot: send a direct email or in-platform message to every consumer in the consumer registry that has called the deprecated endpoint within the past 90 days, on the day the deprecation is declared. Active users of an endpoint do not read the documentation for that endpoint — the yellow "Deprecated" banner in Redoc is addressed to future evaluators, not current users. The migration guide must be actionable in the email body itself: the old endpoint, the new endpoint, a code example showing the authentication change and the response shape change side-by-side, and a reply-to address for questions. Send a reminder 30 days before the sunset date to any account still calling the deprecated endpoint at that time. Do not remove the endpoint until the access logs show that calls from non-migrated consumers have dropped to zero, or until the sunset date has passed and remaining callers have been personally contacted by support.
How do you keep API documentation examples accurate when the implementation changes?
For code-first documentation: add a CI step that validates every static example in the spec against its schema definition using a JSON Schema validator. This catches examples that were added for one field type and were not updated when the field type changed. Static examples are the only claim in a code-first spec that can diverge from the implementation — schema types and field names are regenerated from the code. For hand-maintained documentation: maintain a contract test suite that calls the live API against a staging environment and asserts on field types, formats, and shapes using the documented spec as the assertion source. The contract tests run on every deploy and will fail when the implementation diverges from the documented type. Additionally, specify a documentation update requirement in the contribution guidelines: any PR that changes a response field's name, type, format, or example value must include an update to the corresponding documentation file; a documentation linter that enforces co-modification of spec files alongside source files makes this visible in code review. For behavior changes that fix previously incorrect documented behavior: treat any behavior change that was correctly documented at the time consumers built against it as a breaking change, regardless of whether the old behavior was intended. The consumer who built against the documented epoch timestamp cannot distinguish "documented bug" from "documented contract" — both were the authoritative source when they built their parser. Notify them through the consumer registry outbound mechanism before deploying the fix.
Further reading
- API versioning strategy decision record — the versioning model, the version increment semantics, and the parallel-version support window that determine what a version bump communicates to consumers and how long the previous version is maintained after a breaking change; the spec diff gate's required version increment is meaningful only when the versioning strategy specifies the migration commitment that comes with a new version number.
- API contract testing decision record — the consumer-driven contract testing model, the provider verification cadence, and the contract evolution workflow that catch API schema violations before they reach consumers; documentation accuracy tests (does the implementation match the spec?) and consumer-driven contract tests (does the implementation match what consumers have built?) are complementary and both belong in CI.
- API deprecation strategy decision record — the deprecation policy, the when-to-deprecate-versus-version decision, and the operational cost model for maintaining deprecated endpoints; the deprecation lifecycle in the documentation strategy decision (notification process, sunset timeline, response header protocol) pairs with the deprecation policy decision to form a complete deprecation governance framework.
- Webhook delivery decision record — webhook payload schemas are API contracts that integration consumers build parsers against, and a change to a webhook payload shape carries the same breaking change classification and consumer notification requirements as a REST endpoint schema change; the consumer registry must include webhook consumers alongside REST consumers to ensure that deprecation and breaking change notifications reach both audiences.
- Observability strategy decision record — the metrics instrumentation and distributed tracing model that surface consumer-side errors attributable to field type mismatches and schema validation failures; a spike in consumer-side type errors is the production signal that an undocumented behavior change has reached a consumer who built against the previous documentation, and the observability strategy must include consumer-error attribution (which API key, which field) to enable proactive outreach before the support ticket arrives.
- Open-source extractor — find the API documentation decisions buried in your AI chat history: the session where the framework's automatic OpenAPI generation was adopted as "good enough documentation" without deciding whether the spec was a contract, the session where a deprecation flag was added to the spec because "we'll email consumers later," and the session where a field type was changed as a bug fix without a consumer notification because "it was already wrong" — each of these is an undocumented decision whose absence compounds into integration partner incidents months later.