Claude Fable 5.1 & GPT-6 Astra packages are live

Versioning

Free

Versioning an HTTP API without stranding clients — what counts as breaking, where the version lives, and how to deprecate on a schedule people can…

213 lines8.7 KB Grok API
targetModels
Grok 4.6Grok 4.5Grok 4 FamilyGrok Code FastFuture Grok Models
name
versioning
category
API
description
Versioning an HTTP API without stranding clients — what counts as breaking, where the version lives, and how to deprecate on a schedule people can plan around.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Grok: scripts/model-profiles.json -->

#Non-negotiable

The constraints hoisted below override anything later in this document. Read them first; the rest is rationale.


#Non-negotiable constraints

These override anything later in this document.

  • Never remove a version without per-consumer usage data. Log the version on every request, aggregate by API key, and contact the remaining callers directly. Removing a version you have not measured is how an unannounced outage happens.

#Purpose

Rules for evolving a published API. The goal is not to avoid change — it is to make change predictable, so integrators can plan. Every version you support is a permanent maintenance cost, so the second goal is to need as few as possible.


#What is breaking

ChangeBreaking
Adding an optional response fieldNo
Adding an optional request fieldNo
Adding a new endpointNo
Adding a new enum valueYes — clients with exhaustive switches fail
Removing or renaming a fieldYes
Changing a field's type or nullabilityYes
Making an optional request field requiredYes
Tightening validation on an existing fieldYes
Changing a default valueYes
Changing pagination or sort orderYes
Changing an error code for the same conditionYes
Changing HTTP status for the same conditionYes

Two of these are routinely missed. New enum values break clients that switch exhaustively — document from day one that clients must handle unknown values, and ship an "unknown"/default branch in your own SDKs. Tightened validation turns previously-accepted requests into 400s; it is breaking even though the schema looks unchanged.

Rule of thumb: if a request that worked yesterday now fails, or a response a client parsed yesterday now fails to parse, it is breaking.


#Where the version goes

LocationExampleTrade-off
URL path/v1/ordersVisible, cacheable, trivially routable. Default.
HeaderAPI-Version: 2026-08-23Clean URLs; invisible in logs and curl; easy to forget
Date-based headerStripe-style, pinned per accountBest for large surfaces; most machinery to build
Query parameter?version=1Easy to drop accidentally; pollutes caching
Accept media typeapplication/vnd.acme.v1+jsonCorrect by the spec; awkward in practice

Use a major version in the path unless you have a specific reason not to. It survives proxies, appears in every log line, and a developer can read it off a curl command.

Version the API surface, not each endpoint. Per-endpoint versions produce a matrix nobody can reason about and a client that must track dozens of numbers.

Pin the version per integration, not per request, for large surfaces: the account's version is recorded at signup and applied to all its calls, with an explicit opt-in upgrade. This is what lets you ship breaking changes without breaking anyone.


#Prefer expansion to a new version

A new major version doubles your test surface and your on-call burden. Before cutting one, check whether the change fits inside the current version:

  • Add alongside. Ship amountCents next to a deprecated amount, populate both, and remove the old one at the next major.
  • Opt-in behaviour. A request field or header selects the new behaviour; absent means the old behaviour.
  • New endpoint. POST /v1/orders/bulk beside POST /v1/orders avoids redefining an existing contract.

Reserve a major version for changes that genuinely cannot coexist — a restructured resource model, a changed authentication scheme.


#Deprecation

Announce, signal, then remove. Never remove without all three.

vbnet
Deprecation: Sun, 01 Mar 2026 00:00:00 GMT
Sunset: Wed, 01 Sep 2026 00:00:00 GMT
Link: <https://docs.example.com/migrate/v2>; rel="deprecation"
Warning: 299 - "v1 is deprecated; migrate to v2 by 2026-09-01"

Deprecation and Sunset are standard headers (RFC 8594 for Sunset) and tooling reads them. Emit them from the moment of announcement.

A workable timeline for a public API:

StageTiming
Announce, publish a migration guideT
Deprecation / Sunset headers liveT
Direct email to identified users of the old versionT, T+3mo, T−1mo
Brownout: short scheduled outages of the old versionT+9mo
RemovalT+12mo minimum

Never remove a version without per-consumer usage data. Log the version on every request, aggregate by API key, and contact the remaining callers directly. Removing a version you have not measured is how an unannounced outage happens.

Brownouts — deliberately failing the old version for ten minutes, announced in advance — surface the integrations whose owners never read email, while the fix is still cheap.


#Contract enforcement

Version drift is a testing problem before it is a policy problem.

bash
# Fail the build on a breaking OpenAPI change
oasdiff breaking spec/v1.openapi.yaml spec/v1.openapi.new.yaml --fail-on ERR
  • Keep an OpenAPI document per major version, generated from the code where possible, and diff it in CI. → API/open-api
  • Contract tests run the previous version's recorded requests against the current build. A test suite that only tests today's shape cannot detect a break.
  • Publish a changelog per version with dates, and link it from the docs.
yaml
# .github/workflows/api-contract.yml
- name: Detect breaking API changes
  run: |
    git show origin/main:spec/v1.openapi.yaml > /tmp/base.yaml
    npx oasdiff breaking /tmp/base.yaml spec/v1.openapi.yaml \
      --fail-on ERR --format githubactions
ToolRole
oasdiffBreaking-change detection between two OpenAPI documents
openapi-diffAlternative differ; JSON output for dashboards
pact / pactflowConsumer-driven contract tests across services
schemathesisProperty-based fuzzing against the OpenAPI document
buf breakingThe equivalent gate for gRPC/protobuf surfaces

Record the version on every request as a structured log field (api_version="v1", consumer_id=…) so usage is queryable by consumer. That field is what makes a removal decision defensible. → Backend/logging


#Anti-patterns

Anti-patternWhy it failsFix
No version at allThe first breaking change strands every clientVersion from the first release
Version per endpointUnreasonable matrix for clients and testsVersion the surface
Adding an enum value silentlyExhaustive client switches failDocument unknown-value handling; treat as breaking
Tightening validation in placePreviously valid requests start failingNew version or opt-in
Removing a field "nobody uses"Nobody measuredPer-consumer usage logging
Deprecating without headersTooling and clients never see itDeprecation + Sunset
Sunset date under six monthsIntegrators cannot plan12 months for a public API
Version only in a headerInvisible in logs and support ticketsPath version
Supporting versions indefinitelyUnbounded maintenance and security surfaceEnforce the sunset
Cutting a major for an additive changeDoubles cost for nothingAdd alongside
No OpenAPI diff in CIBreaks ship unnoticedoasdiff gate

#Checklist

  • Verify: A version identifier is present from the first public release
  • Verify: The major version is in the URL path, or a documented pinned-per-account scheme
  • Verify: The API surface is versioned as a whole, not per endpoint
  • Verify: The list of breaking changes is written down and agreed by the team
  • Verify: Clients are documented as required to ignore unknown fields and enum values
  • Verify: Additive changes are preferred over new major versions
  • Verify: Version usage is logged per consumer and reviewed before any removal
  • Verify: Deprecation and Sunset headers are emitted from announcement
  • Verify: A migration guide is published alongside the deprecation
  • Verify: The sunset window is at least 12 months for a public API
  • Verify: Brownouts are scheduled and announced before removal
  • Verify: CI fails on a breaking OpenAPI diff within a version