What disciplined API versioning looks like under a merge gate
Delimit's merge gate ran against the public Stripe OpenAPI on 2026-05-05. The change set between commit da3eadb2 (2026-02-23) and d771243f (2026-04-20) spans 57 days and one declared API version bump from 2026-02-25.clover to 2026-04-22.dahlia. This page is the signed, replayable attestation that fell out, written up so any reader can audit the same evidence bytes.
github.com/stripe/openapi, tags monthly API versions, and is widely regarded as the reference for disciplined public-API versioning. The analysis is reproducible byte-for-byte from the same commits. We did not contact Stripe, did not coordinate this report with Stripe, and have no relationship to the project beyond reading the public spec.What we did
We cloned github.com/stripe/openapi, extracted openapi/spec3.json at two points: the most recent spec update at the time of analysis (commit d771243f, 2026-04-20, declaring API version 2026-04-22.dahlia) and the preceding monthly release roughly 57 days earlier (commit da3eadb2, 2026-02-23, declaring API version 2026-02-25.clover). Each spec is roughly 7.4 MB of JSON and declares 414 endpoints. We ran delimit lint in its standard configuration, with no policy overrides. The diff engine classified each change against its 27-type taxonomy. The semver classifier walked the breaking-vs-non-breaking partition and produced a bump recommendation. Everything below is mechanical output, annotated.
Headline numbers
The endpoint count is identical across the window: zero added, zero removed. The change volume is concentrated in component schemas and in enum surfaces (52 schemas added, 106 enum values added across many endpoints). The breaking subset is small and clustered. The advertised info.version field changed from 2026-02-25.clover to 2026-04-22.dahlia, which is Stripe's monthly release naming. The gate's independent semver classification is major, and Stripe's declared version bump is the dahlia release. The two artifacts agree.
Findings
5 breaking, 2 additive, 1 flagged as spec hygiene. Each finding cites the exact change-type from the 27-type taxonomy, the surface affected, and the consumer impact. Hygiene-class findings are real lint output that a careful reader will re-classify on reading; we surface both classes so the report stays honest.
- breakingfinding F1change type: enum_value_removed (3) plus enum_value_added (3)surface:
/v1/checkout/sessions request.ui_mode and checkout.session.ui_modeThe ui_mode enum on Checkout Sessions was rewritten in place. The prior values custom, embedded, and hosted were removed and replaced with elements, embedded_page, and hosted_page. The new spec carries an x-stripeBypassValidation: true marker on the field, which is Stripe internal annotation that the field has known compatibility handling outside of the schema. A consumer pinned to the prior enum will fail validation if it sends the legacy values against the new schema. The rename pattern is consistent across both the request body and the response schema, which is the disciplined version of this kind of change. The visibility is the artifact: any reviewer reading the attestation sees the enum rewrite without having to re-derive it from the diff.
- breakingfinding F2change type: field_removed plus required_field_added (paired rename)surface:
/v1/test_helpers/issuing/authorizations request.risk_assessment.merchant_dispute_risk and request.risk_assessment.card_testing_riskOn the test-helpers issuing surface, the required field risk_level was removed from both merchant_dispute_risk and card_testing_risk and a new required field level was added in its place. Same shape, same string enum, different name. This is a clean field rename, but the rename is a contract change for any test harness that constructs risk_assessment payloads against the prior shape. Stripe runs test_helpers under the same versioning discipline as the public surface, which is why the gate flags it the same way it would flag a rename on /v1/charges.
- breakingfinding F3change type: field_removed plus required_field_added (schema refactor)surface:
#/components/schemas/radar.payment_evaluationThe Radar payment_evaluation schema dropped the required insights field and added two new required fields: recommended_action and signals. Two supporting schemas, insights_resources_payment_evaluation_insights and insights_resources_payment_evaluation_scorer, were removed entirely from components.schemas. A new family of insights_resources_payment_evaluation_signal_v2 and insights_resources_payment_evaluation_signals schemas replaces them. This is the substantive shape change in the window. Code reading evaluation.insights against the prior shape will see undefined; code expecting the prior outcome semantics needs to re-target evaluation.recommended_action and walk evaluation.signals.
- breakingfinding F4change type: required_field_addedsurface:
#/components/schemas/invoiceitem.quantity_decimalinvoiceitem now requires quantity_decimal in addition to quantity. The field is new on the schema and it is on the required list from day one. Code that constructs InvoiceItem fixtures or deserializes them against the prior shape needs to populate or accept the new decimal-precision field. The pattern is consistent with how Stripe has staged decimal-precision fields elsewhere on the billing surface.
- breakingfinding F5change type: required_field_added (Connect embedded components)surface:
#/components/schemas/connect_embedded_account_session_create_components.balance_report and .payout_reconciliation_reportTwo new required fields landed on the Connect Account Session create-components schema: balance_report and payout_reconciliation_report. Each is a sub-component descriptor with its own enabled flag and feature set. Connect integrations that build account-session components programmatically must now declare both, even if they were not previously aware of either component. The required-from-day-one declaration is what makes this breaking; an optional declaration would be additive.
- additivefinding F6change type: enum_value_added (106 across the surface)surface:
/v1/payment_methods, /v1/setup_intents, /v1/payment_intents, /v1/checkout/sessions, /v1/payment_links and the supporting payment-method enumsTwo new payment methods, sunbit and upi, were added across every endpoint and schema that enumerates payment-method types. That single product decision drives roughly two thirds of the additive enum changes in the window. Other additions include new enum values on tax-id types (3 new ID schemes per surface), balance_transaction.type (one new transaction class), and incremental enum additions on excluded_payment_method_types parameters. None of this breaks existing clients. A client that does not handle the new values gracefully on output will see them surfaced through, but the API contract for input acceptance is unchanged. This is the substance of disciplined surface expansion.
- additivefinding F7change type: schemas_added (52)surface:
components.schemas (UPI, sunbit, Klarna QR, payment-record sub-types)Fifty-two new component schemas were added in the window. The dominant clusters are UPI payment-method support (mandate_upi, mandate_options_upi, payment_intent_next_action_upi_handle_redirect_or_display_qr_code, checkout_upi_payment_method_options, invoice_payment_method_options_upi, invoice_payment_method_options_mandate_options_upi, payment_intent_next_action_upiqr_code), Klarna QR-code next-action handling (payment_intent_next_action_klarna_display_qr_code), an issuing card-lifecycle controls family (issuing_card_lifecycle_conditions, issuing_card_lifecycle_controls), and the Radar signals refactor (see F3). All additions, no breaks.
- spec hygienefinding C1change type: type_changed (string to anyOf, expansion-friendly refactor)surface:
#/components/schemas/billing_bill_resource_invoicing_taxes_tax_rate_details.tax_rateThe diff engine reported the tax_rate field type changed from string to None. The actual change is string to anyOf(string, $ref to tax_rate schema), with an x-expansionResources hint indicating the field is now an expandable Stripe resource. The engine resolved the new shape conservatively because anyOf without a top-level type is ambiguous to a strict reader. A downstream consumer that only reads tax_rate as a string id will continue to work; a consumer that opts into expansion will receive the full tax_rate object. The change is non-breaking in practice but the gate surfaces it because the schema shape did change. Worth surfacing in attestation review even though the runtime contract is preserved.
What this report is not
Not a defect claim. Not a security advisory. Not a judgment of the Stripe team's release process. Stripe ships a deeply versioned public API with monthly version codenames (clover, dahlia, and the successor releases) and an explicit upgrade path between every pair. The breaking changes flagged above are intentional, are part of an announced version bump, and are documented in the Stripe API upgrade log for the dahlia release. The merge gate's job is making the shape of those intentional changes visible in pre-merge review and confirming that the version string downstream consumers see matches the change class.
The findings above do not say Stripe made a mistake. They say: here is exactly which fields, schemas, and enums shifted between clover and dahlia, and here is what a downstream consumer pinned to the clover shape will need to re-map. That visibility is the artifact. Stripe is the worked example because Stripe is what disciplined versioning looks like; the gate's output and the Stripe-declared version bump agree, and an attestation reader can verify that agreement without re-running the analysis.
The attestation artifact
A Delimit attestation is a bounded evidence record at a single commit pair. The same Delimit version run against the same two commits produces the same bytes; that is the replayable property. The attestation does not opine on whether a change should have shipped, only on what shipped and how the change-type taxonomy classifies it.
For the precise list of checks, the explicit out-of-scope list, and the reproducibility guarantee, see the attestation methodology v1. This report is the OpenAPI-diff surface of the same primitive that powers the merge gate for AI-written code.
Reproduce locally
Anyone can re-run the analysis above against the same two commits and verify the same diff comes out. The full command sequence:
# Install the CLI npm install -g delimit-cli # Clone the repo (full history; we walk back ~57 days) git clone https://github.com/stripe/openapi cd openapi # Extract the two specs at the cited commits git show da3eadb2:openapi/spec3.json > /tmp/old.json git show d771243f:openapi/spec3.json > /tmp/new.json # Run the merge gate delimit lint /tmp/old.json /tmp/new.json
If the bytes you get differ from the bytes in this report, that is itself a finding worth reporting; raise it on the Delimit repo and we will look. Each spec is roughly 7.4 MB; the lint pass completes in under one second on a standard laptop.
For your own API surface
If you ship a public API and want this kind of pre-merge attestation in your CI pipeline, install delimit-cli and run delimit lint <old> <new> against your own specs. The GitHub Action is on the Marketplace at delimit-ai/delimit-action. Free for individual maintainers. Pro tier $10/month for teams.
The signed, replayable attestation is the artifact your reviewers, auditors, or downstream consumers can read without rerunning the gate.