Tunas Akara
Back to Blog

API Versioning When Vendors Don't Version Anything

by RayhanUpdated 8 min read
apiintegrationversioningreliability
API Versioning When Vendors Don't Version Anything

API Versioning When Vendors Don't Version Anything

On the evening of January 12, 2023, Tweetbot, Twitterrific, Echofon, Fenix, Twitpane, Feather, and Talon all stopped working at once. Users couldn't log in. Every API call failed, and the apps showed as "Suspended" in Twitter's developer portal — with no advance notice.

Developers guessed at the cause that same evening. A week later, new developer-agreement terms made the ban permanent.

None of those teams shipped broken code. Their integrations were fine right up until the vendor changed the rules underneath them — no deprecation notice, no version to roll back to. That's the failure mode this article covers: depending on an API someone else controls, who doesn't feel obligated to version it.

Think of a vendor API like a toll road that can reroute overnight with no signs warning you. Most integrations sit somewhere between "disciplined like Stripe" and "no warning like Twitter in 2023." Assume the worse end. You can't negotiate your way out of someone else's roadmap.

What good versioning looks like

URL path or query-param versioning (/v2/orders) is the most common and easiest to reason about. Custom header versioning keeps URLs stable, but it's easy to forget when a client library doesn't set the header. Media-type versioning (Accept: application/vnd.vendor.v2+json) is the most correct REST answer, and the one fewest vendors bother with.

Stripe pins every account to a dated version, such as 2026-07-29, from the first call. Later calls use that same version unless overridden via Stripe-Version. You can dry-run an upgrade from staging first. (docs, upgrade guide)

GitHub recommends X-GitHub-Api-Version on every request. Skip it, and GitHub defaults to a fixed version, not "latest." Each version gets 24+ months of support with Deprecation/Sunset warning headers, then returns 410 Gone once retired. (docs)

Shopify cuts a dated release quarterly, guarantees 12 months of support with 9 months of overlap, and falls forward instead of hard-failing: a request pinned to retired 2026-10 gets served as 2027-01. (docs)

Most vendors you'll actually integrate with — a regional payment aggregator, a hardware SDK, a channel manager — offer none of this. You get one version, called "the API," and it changes whenever the vendor's roadmap says so.

Pattern 1: the tolerant reader

Martin Fowler's Tolerant Reader pattern applies Postel's Law to parsing. Pull only the fields you need out of a response. Ignore everything else, and default what's missing instead of throwing.

Fowler's example uses XPath: query //order instead of a rigid absolute path, so a vendor inserting a wrapper element doesn't break you. Applied to JSON: read response.customer.email directly, don't validate the whole object as a strict struct, and never assume field order.

Fowler links to a post by Ian Cartwright that flags something worth internalizing: strict schema validation gives "a false sense of security." A schema pinning every field rejects a harmless change as hard as a breaking one — it can't tell the difference. The other half of the pattern: centralize that parsing in one place, usually a DTO, so one spot in the codebase knows the vendor's real response shape.

Pattern 2: contract tests as a tripwire

Fowler's Consumer-Driven Contracts pattern was built for internal teams. Each consumer writes assertions describing which fields and behaviors it depends on. The provider runs every contract against its own build, for fast feedback before shipping a breaking change.

You can't insert your contract into a vendor's pipeline. But you can write it and run it yourself against their sandbox on a schedule — a tripwire, not a merge gate. Assert the exact fields, types, and status codes your integration relies on, and run that check nightly. When a field silently disappears or changes type, the test fails hours before a customer notices.

Pattern 3: the anti-corruption layer

Give the vendor's data shape exactly one place to enter your system.

Loading diagram…

Without this boundary, a vendor renaming customer_email to contact_email means a grep across every service that ever touched an order. With it, that rename is a one-file fix inside the layer. Everything downstream never learns the field name changed — the parsing logic lives inside this one boundary, nowhere else.

Pattern 4: pin and monitor

Where the vendor supports it, pin to a specific version instead of floating on whatever the API returns today. Mid-tier vendors increasingly copy Stripe's dated-version model, minus the discipline. Pinning turns an involuntary break into an upgrade you schedule.

Where pinning isn't available — most vendors — monitoring is what's left. Run the pattern-2 contract tests against production too, not just sandbox: the two drift, or sandbox never gets touched at all. A scheduled diff of today's response shape against yesterday's catches drift within hours, before it reaches a customer as a broken feature.

Put change-notification duties in the contract

Technical defenses only slow the bleeding. The other half is contractual, and it's worth doing even though most vendors won't reach Stripe or GitHub's discipline.

Microsoft's REST API Guidelines define a breaking change generically as anything affecting the contract or backward compatibility, then require each team to write its own concrete list: Azure treats a new JSON response field as breaking, Office 365 doesn't. Borrow that model. Make the vendor hand you a specific definition, not a vague promise.

LinkedIn's breaking-change policy is easier to lift directly. Breaking changes — removing a field, adding a required parameter with no default, a DELETE switching from soft- to hard-delete — get advance email notice. Non-breaking changes, like a new optional field, ship with no notice.

LinkedIn also reserves the right to skip notice for critical bug, legal, security, or privacy fixes. Read that carve-out closely: it covers exactly the kind of change that breaks integrations.

Ask for three things: a written definition of "breaking," a minimum notice period, and a real notification channel — email or webhook, not a changelog nobody checks. Most vendors will agree, since a promise costs them little. That's why you build the four technical patterns regardless of what the contract says.

What to demand, and what to build anyway

Before signing: a written breaking-change definition, a minimum notice period, a real notification channel, and a pinnable version if they offer one.

Build regardless of what they promise: a tolerant reader as the sole thing touching the vendor's raw response, and contract tests on a schedule against sandbox and production. Add an anti-corruption layer as the single translation point, and monitoring that would have caught January 12, 2023 within the hour — not from angry users. A vendor's promise is a mitigation. Your own defenses are what holds when it doesn't.

If your integrations span multiple hardware or protocol types rather than one REST API, the same translation-boundary idea shows up in normalizing multi-protocol IoT devices. Different problem, same fix.

Related Posts

Building something similar?

IoT Backend & Multi-Protocol Integration

Backends that ingest device telemetry across MQTT, WebSocket, Modbus, and BLE, and normalize it into reliable real-time dashboards.

See how I can help