Hermes Wiki
Developer/CommunicationPatterns/Protocols/REST/CaseStudies/github-calendar-based-rest-api-versioning

GitHub: Calendar-Based REST API Versioning Instead of URL Paths

Problem + constraints

GitHub's REST API had been on "v3" for over a decade by the time the company revisited its versioning strategy — not because v3 stopped working, but because a single, unchanging version number gave GitHub no clean way to make an occasional breaking change (renaming a field, changing a status code's meaning, tightening validation) without either breaking every existing integration simultaneously or freezing the API's evolution indefinitely. The constraint wasn't technical so much as organizational: GitHub's REST API has an enormous, uncoordinated population of third-party integrators — CI systems, IDE plugins, internal tooling at other companies — none of whom can be forced to update on GitHub's schedule, and many of whom will never proactively opt in to a new version even when one exists.

The conventional fix, URL-path versioning (/v4/repos/...), was available but came with its own cost: every breaking change would fork the entire URL space, client SDKs would need separate code paths per major version, and the version number would become a permanent, highly visible part of every integration's codebase rather than an incidental request detail.

Solution

GitHub introduced calendar-based versioning delivered via a request header, X-GitHub-Api-Version, instead of a URL path segment. A version is identified by the date it shipped (e.g. 2022-11-28), not a sequential major number, and a client selects its version per request by setting that header — omitting it entirely falls back to a fixed default version rather than failing. Critically, versioning is reserved only for breaking changes: non-breaking additions (new optional fields, new endpoints) roll out to every version simultaneously, so the vast majority of API evolution never requires a client to touch the header at all. When a genuinely breaking change does ship under a new dated version, the previous version stays fully supported for at least 24 months afterward, giving integrators a long, predictable migration window instead of a hard cutover.

The header-based approach specifically avoids the URL-fork problem: the resource URLs themselves (/repos/{owner}/{repo}) never change shape across versions, so a client upgrading from one API version to the next changes one header value rather than restructuring every endpoint it calls or maintaining parallel SDK code paths. GitHub had already tried a version-in-header pattern for its GraphQL and internal APIs; extending it to REST kept the versioning mechanism consistent across GitHub's own API surface rather than introducing a second, URL-based scheme just for REST.

What to steal

  • Decouple "this changed" from "where do I call it." Putting the version in a header rather than the URL means a resource's identity (its URL) stays stable across the API's entire lifetime — only the shape of what's returned or accepted changes, addressed by a request-scoped detail instead of a permanent path segment baked into every client integration.
  • Version only what actually breaks. Treating additive, backward-compatible changes as unversioned (available to every client immediately) keeps the versioning mechanism reserved for the rare case that actually needs it, instead of forcing a version bump — and the coordination cost that comes with one — on every change.
  • A generous, explicit support window (24 months) is a migration strategy, not a courtesy. For an API with integrators GitHub can't coordinate with directly, giving them a long, calendar-anchored deadline is what makes eventually retiring an old version feasible at all — a short window just pushes the coordination problem back onto GitHub's own support load.
  • Calendar-based version identifiers double as a changelog. A version named 2022-11-28 tells an integrator when a behavior was introduced without needing a separate mapping table from version number to release date — a small but real ergonomic win over sequential major versions.

Engineering Lens

This case is a good illustration of versioning as an integration-coordination problem, not a technical one — the header-vs-URL choice barely matters for how a single client parses a response, but it matters enormously for how thousands of uncoordinated integrators experience change over years. The design GitHub landed on optimizes specifically for the constraint that actually bites at GitHub's scale: most callers should never have to think about versioning at all (non-breaking changes are free), and the rare caller who does needs a change that costs them one header value and a multi-year deadline, not a URL rewrite. The transferable lesson for any long-lived public API is to separate "does this response shape change" from "does this resource's address change," and to reserve the expensive, highly visible part of versioning (a URL fork, a hard support cutoff) for the minority of changes that are genuinely breaking.

Sources

Hermes Wiki