Versioning
Pinning API versions: Stripe-Version, X-GitHub-Api-Version, and what pinning does not protect
Stripe and GitHub both pin you by default, to different things. How each model works, a worked Stripe upgrade drill with real spec numbers, and the drift a pin cannot stop.
Published 24 October 2026 · 6 min read
A pinned API version is the closest thing REST vendors offer to pinning a package. Stripe and GitHub both version by date, and both put a request on a fixed version when you send no header, but the version they choose for you is different, and so is what changes underneath it. This article compares the two from their own documentation (read on 23 October 2026), walks through a Stripe upgrade drill with numbers from the published specs, and lists the drift that no version pin stops.
Stripe: your account default, unless you say otherwise
Per Stripe's versioning reference, a request made without an SDK uses your account's default API version, which is controlled in Workbench, unless you send a Stripe-Version header. Webhook events also use the account default unless you set a version when you create the endpoint. Since the 2024-09-30.acacia release, Stripe ships monthly versions with no breaking changes, and twice a year a major release (Basil is the example the page gives) that starts with a version containing breaking changes. The current version on that page is 2026-09-30.endive.
SDKs add a layer. Stripe's upgrade guide says a server-side SDK calls the API with the version that was current when that SDK version was released, and that you cannot target a different version from a strongly typed language such as Java, Go or .NET. In stripe-ruby, the guide's example, you can set the version globally or per request. The guide's first instruction is to specify the version in your code rather than rely on the account default.
One Stripe-specific wrinkle matters: enums. The versioning reference says some properties use closed enums, whose values never change, and others use open enums, which Stripe can grow at any time as a backward-compatible change without an API version upgrade. Its guidance is to not assume the documented values of an open enum are exhaustive and to give your code a safe fallback, such as a default branch in a switch statement. If your parser handles exactly the values in your pinned version's docs, an open enum is the hole in your pin.
GitHub: the oldest supported version, unless you say otherwise
GitHub's API versions page names versions by release date and says you should send X-GitHub-Api-Version to choose one. Requests without the header default to 2022-11-28. Two versions are listed as supported: 2026-03-10, with no end of support scheduled, and 2022-11-28, supported until 10 March 2028. When a new version ships, the previous one is supported for at least 24 more months.
The page lists what counts as breaking: removing an operation, removing or renaming a parameter or response field, adding a required parameter, making an optional parameter required, changing a type, removing enum values, adding a validation rule to an existing parameter, and changing authentication or authorization requirements. Breaking changes ship only in a new version. Additive changes, including new response fields and new enum values, reach every supported version.
Two details matter for anyone who sends no header. While a version is approaching the end of its support window, GitHub includes Deprecation and Sunset headers in responses. After the window ends, a request that names the retired version gets 410 Gone, and a request with no version header moves to the next oldest supported version. The page warns that integrations relying on unversioned requests may observe behavior changes as old versions leave support. Not sending the header is a pin with an expiry date that you did not choose.
GitHub also lists exceptions: for critical security, availability or reliability issues it may, in rare cases, make a breaking change to an existing version, and for some very low-usage services it may deprecate functionality outside the versioning process and contact affected integrators directly.
The two models side by side
- No header sent: Stripe uses your account's default version (or, through an SDK, the version that SDK release was built for); GitHub uses 2022-11-28.
- Choosing a version: the Stripe-Version header, or SDK configuration in languages that allow it; the X-GitHub-Api-Version header on each request.
- Breaking changes: Stripe puts them in the first version of a twice-yearly major release; GitHub puts them in a new dated version.
- Webhooks: Stripe fixes an endpoint's version when the endpoint is created; to change it you create a new endpoint, per the upgrade guide.
- Retirement: GitHub supports a superseded version for at least 24 months and then returns 410 Gone for it. Stripe's pages read for this article give no end date for old versions.
What pinning protects, and what it does not
Pinning freezes the contract you coded against, so removals, newly required fields and type changes cannot reach you while you stay put. What it does not do:
- Additive response fields still appear. GitHub says so explicitly: additive changes are available in all supported versions. Code that assumes a closed response shape is exposed either way.
- Enum values can grow. Stripe's open enums grow without a version change, and GitHub lists added enum values as additive. Unknown values must be handled, not rejected.
- Vendors keep exceptions. GitHub reserves the right to break an existing version for security, availability or reliability reasons.
- A pin ages. GitHub's 24-month window is a countdown, and an unversioned GitHub client is moved forward automatically when its default version retires.
A worked upgrade drill
Suppose you are pinned to Stripe 2026-03-25.dahlia and want to know what 2026-09-30.endive would do to you. The upgrade guide's path is to test the new version by sending the Stripe-Version header before you change the account default. A cheaper first step is to diff the two published specs. APIBreak's diff of Stripe's OpenAPI repository, taken on 27 September 2026, compares a dahlia-era snapshot (23 March 2026) with an endive-era one (25 September 2026): 587 operations against 612, 25 added, none removed, none newly deprecated.
None removed does not mean nothing breaks. The same diff finds 18 request fields that the newer spec no longer lists, across 11 operations. Among them: POST /v1/checkout/sessions drops payment_method_types and line_items[].dynamic_tax_rates; payment_method_types is gone from the create, update and confirm calls for PaymentIntents and from create and update for SetupIntents; POST /v1/accounts drops documents.proof_of_registration; and POST /v1/terminal/refunds drops seven fields. A spec diff cannot show whether Stripe rejects or ignores a removed field at runtime. For the Checkout case, Stripe's changelog says it directly: passing payment_method_types when you create a Checkout Session now returns a 400 error, and the replacements are dynamic payment methods, allowed_payment_method_types or excluded_payment_method_types. The full list is on the Stripe change page.
That is the drill. The new version cannot break deployed code that stays pinned to dahlia, but the first request you send with Stripe-Version: 2026-09-30.endive that still includes one of those fields is, at best, untested and, for Checkout, a 400. Diff first, fix the call sites the diff names, then move the pin.
Checklist
- Set the version explicitly in code (Stripe-Version or SDK configuration; X-GitHub-Api-Version on every GitHub request). Do not rely on an account default or an implicit fallback.
- Record the pinned version in the repository next to the integration code, so a diff between your pin and any candidate is one command.
- Diff the vendor's published spec before every upgrade, filtered to the operations you call, and treat removed request fields and removed enum values as work items.
- Treat a deprecation flag in the spec, or a Deprecation or Sunset response header, as the start of your upgrade calendar.
- Audit parsers for open enums and unexpected response fields: an unknown value should log, not throw.
Pinning turns vendor change from a constant drip into a scheduled decision. The tooling that makes the decision well informed is the same spec diff that watches vendors you cannot pin: keep a baseline, diff forward, fix what the filter surfaces, move the pin.
Check the endpoints you actually call
APIBreak reads the endpoints you declare, compares the vendor's own OpenAPI specification against the revision you pinned, and fails the build only when a change touches one of yours. It runs in your CI with no credentials and nothing uploaded, and the check is free.
Install the check · read a real report · what changed at each vendor