Build Daily

Tinley Park · July 25, 2026

Backwards compatibility is the job

There's a promise in enterprise SaaS that nobody writes into the contract and everybody enforces anyway: the integration shipped three years ago still works today. Not "works if they upgrade." Not "works with a small migration." Works — untouched — while half the system underneath it has been rewritten.

Consumer software gets to break things. Ship a redesign, users grumble for a week, they adapt. Enterprise software doesn't have that door. The customer didn't just use the product — they built systems on top of it. A nightly job that parses the CSV export. A middleware that maps the webhook payload into their ERP. An integration written by a contractor who left in 2023, that nobody dares touch, that finance depends on to close the month. When the shape of a field changes, none of that grumbles and adapts. It breaks, silently, in their production, and the first anyone hears of it is an escalation with the vendor's name on it.

A decade building enterprise multi-tenant SaaS inverts the instinct picked up in startups — move fast, refactor freely, the schema is the team's own. The contract is not the team's to change. It belongs to everyone who integrated against it, and the job is to keep evolving the product without ever breaking the promise underneath them.

§ 01
CHAPTER

The contract is bigger than the API

The first mistake is thinking "the contract" means the documented API. It's much larger than that. It's every observable behavior a customer could have come to depend on — and they depend on far more than was ever documented.

The real contract surfaceThree nested rectangles. The smallest, innermost box is the documented API. Around it, a larger box is undocumented but observable behavior: field ordering, error message text, timing, null handling. The largest outer box represents Hyrum's Law: with enough users, every observable behavior is depended on by someone. The point is that the surface customers depend on is far larger than what was documented.WHAT CUSTOMERS ACTUALLY DEPEND ONHYRUM'S LAW — EVERY OBSERVABLE BEHAVIORtiming · ordering · error text · null handling · even the bugs they worked aroundUNDOCUMENTED BUT OBSERVABLEfield order · response shape · enum values · pagination quirksDOCUMENTED APIthe part mistaken for the contractFIG. 1 — The promise was the green box. Customers built on the whole thing.
↗ click to enlarge

This is Hyrum's Law, and it's the law that governs the whole discipline: with enough users, every observable behavior of the system gets depended on by somebody — regardless of what was documented. The order fields come back in. The exact text of an error message someone regex-matched. A list that happened to be sorted so they never added their own sort. The null sent where they now expect null. The 200ms of latency they built a timeout around. Even bugs: fix one that a customer quietly worked around, and the fix is their breakage. The documented API is the small green box. The thing that actually can't be broken is the whole diagram.

§ 02
CHAPTER

Additive is safe; everything else costs

The load-bearing distinction is between changes that add and changes that alter or remove. Adding is almost always safe, because a well-built consumer ignores what it doesn't know about. Altering the shape of something that's already there is where integrations break.

// SAFE — additive. Old integrations don't see it; nothing they parse moved.
{ "id": 42, "status": "active", "created_at": "…", "tier": "enterprise" }
//                                                  ↑ new field, added at the end

// BREAKING — every one of these silently breaks a consumer somewhere:
//   • rename  status → state
//   • change  "active"  →  "ACTIVE"          (enum value)
//   • change  id: 42    →  id: "42"           (type)
//   • remove  created_at
//   • reorder fields a positional parser relied on

Internalize that table and most day-to-day compatibility takes care of itself: add columns, don't rename them; add enum values carefully (a new status can still surprise a consumer that switch-matched the old set, so even additions have edges); never change a type or drop a field a customer can see. The moment a shape genuinely needs to alter, don't do it in place — reach for the two disciplines below.

§ 03
CHAPTER

Expand, migrate, contract

The pattern that lets a team change anything without a breaking moment is parallel change — expand / contract. Never flip old to new. Run both, move consumers across on their schedule, and only remove the old thing once nobody's on it.

Expand, migrate, contractThree phases left to right. Expand: add the new field or endpoint alongside the old one, both live. Migrate: move consumers to the new one on their own schedule while both keep working, tracked by telemetry. Contract: once telemetry shows no one uses the old one, remove it. A bar under all three shows the old and new both alive through expand and migrate, with only new surviving after contract.PARALLEL CHANGEthere is never a moment where old stops working1 · EXPANDadd new beside old — both live2 · MIGRATEmove consumers on their clock3 · CONTRACTtelemetry says 0 users → remove oldOLD — still workingNEW — adopted, then the only one leftFIG. 2 — The overlap is the point. The vendor pays to run both for a while so the customer never pays with an outage.
↗ click to enlarge

The other lever is versioning — a new major API version (/v2, or a version header) lets a team make a clean break for new integrations while /v1 keeps its promise to the old ones. It works, but the cost is real: every version published is a version to maintain, test, and secure until the last customer leaves it — and enterprise customers leave slowly. Versioning isn't an escape from compatibility; it's compatibility with a bounded blast radius and a standing maintenance bill. Prefer additive evolution within a version; reserve a new version for changes that genuinely can't be made additively. Semantic versioning is the shared vocabulary for signaling which is which.

§ 04
CHAPTER

Retiring things — carefully

Compatibility forever doesn't mean nothing can ever be deleted; it means nothing can be deleted by surprise. Retirement is a process with four non-optional parts, and the first is the one teams skip:

TELEMETRY
Nothing can be retired that can't be measured. Per-endpoint, per-field usage by tenant — so "is anyone still using this?" is a query, not a guess. Without it, every deletion is a gamble on a forgotten customer.
RUNWAY
Measured in quarters, not sprints. Enterprise change calendars are slow; a 30-day deprecation is a broken promise with a countdown on it.
SIGNAL
Deprecation notices in docs, changelog, and in-band — a Sunset header, a Deprecation warning — so an integration surfaces the end date to the humans who own it, before it bites.
EXCEPTIONS
Sometimes the answer for the biggest account is "never." A contract worth more than the maintenance is a contract worth keeping the old path alive for. That's a business call, made with data — not an engineering shortcut.
§ 05
CHAPTER

What to wire in

Same instinct the whole series runs on: make the system catch the breaking change, so compatibility doesn't depend on every engineer remembering Hyrum's Law at 5pm on a Friday.

01

A schema-diff CI gate

Diff the API/response schema against the last release on every build; fail the pipeline on a breaking change (removed field, changed type, narrowed enum) unless it's an explicit, reviewed version bump. Turns "we hope nobody broke it" into a red build.

02

Consumer-driven contract tests

Let integrators' expectations run as tests against the build — tools like Pact encode "what the consumer actually depends on" so a break shows up in CI, not in their production.

03

Per-endpoint usage telemetry

Who calls what, which fields they read, when they last did. The prerequisite for ever safely deleting anything — and the evidence behind every deprecation decision.

04

An expand/contract migration playbook

A written, repeatable path for shape changes: expand, dual-write/dual-read, migrate consumers, verify zero old usage, contract. So the safe way is the default way, not the heroic exception.

Moving fast is a startup virtue. In enterprise SaaS the virtue is moving without anyone downstream feeling it — and that's a harder, quieter kind of good engineering.

§ 06
CHAPTER

Corrections welcome

The corrections worth having here are the war stories: the Hyrum's-Law dependency that blindsided a team, the version that can never be killed, the deprecation that took longer than the feature, the "harmless" fix that turned out to be load-bearing for someone, the breaking change that shipped anyway and what it cost. That's the knowledge that never makes it into an API style guide.

Email or X. Building in the open is partly a learning project; the outside input is the point.

— Neil

  • #backwards-compatibility
  • #api-design
  • #versioning
  • #b2b-saas
  • #engineering
  • #field-notes
  • #shipping-for-the-enterprise

Continue reading