Architecture Files #28 · Part of Binary and Beyond. LinkedIn newsletter edition follows.
Teams treat API stability as a documentation problem.
Publish OpenAPI. Freeze the schema. Promise "no breaking changes." Put a version number in the path. Call it done.
Then six months later, a partner insists the API changed, engineering insists the schema did not, and both are telling the truth as they understand it. The endpoint still returns 200. The fight is about what the payload was allowed to mean, who was supposed to be told, and whose dashboard is now wrong.
That is the real reason APIs rarely stay stable. Stability is not a property of a file. It is a social contract about meaning, consumers, and compatibility. When nobody names those three things, drift is not a surprise. It is the default.
The version that did not break (and still broke everything)
Picture a mid-market order platform that shipped GET /v1/orders/{id} for a warehouse partner and an ERP sync.
status started as a warehouse enum: open, picked, shipped. Finance later needed to know when payment cleared. Someone tucked paid into the same field because it was "close enough" and the schema still validated. Support later needed partially_shipped. Marketing wanted fulfilled for a campaign trigger.
The path stayed /v1. The type stayed string. Postman still returned 200.
The warehouse treated shipped as carrier handoff. Finance treated paid as cash recognised. The ERP treated missing transitions as bugs. Three teams filed tickets against "the unstable API."
Nothing in the swagger said who owned status, who the consumers were, or what kinds of change required a conversation. So every producer change looked local. Every consumer break looked like sabotage.
Data Contracts Matter More Than APIs made the case that transport is not meaning. This essay is about what happens when meaning has no owner: the API looks frozen while behaviour wanders.
Stability is three jobs, not one schema
When people say "keep the API stable," they usually mean one of three different jobs. Teams that conflate them keep failing the same audit.
Ownership of meaning. Someone must be able to say what a field promises under ugly cases: nulls, partials, refunds, holds. If meaning is "whatever the database has today," every schema-compatible change is a silent product release for every consumer.
Ownership of consumers. Someone must know who depends on the contract, how they use it, and how long they need to migrate. An API with unknown consumers is not a product. It is a rumour with auth tokens.
Ownership of compatibility. Someone must decide what is additive, what is breaking (including semantic breaks with the same types), and how deprecation actually works. "We'll be careful" is not a policy. It is a hope that the next hire inherits.
Swagger can describe shapes. It cannot assign those three jobs. Without them, version numbers become theatre: the URL stays sacred while the business vocabulary mutates underneath.
Why producers drift (and why it feels rational)
Producer teams are scored on features, incidents in their own service, and delivery speed.
Changing a field meaning to unblock a release feels cheap. Notifying partners feels expensive. Writing a deprecation plan feels like paperwork for a change that "shouldn't matter."
Consumers are invisible until they page you. So the incentive is to treat the producer database as truth and the API as a dump of whatever is convenient this sprint.
That incentive is not stupidity. It is an ownership gap. If no one is measured on consumer continuity, consumer continuity will lose every prioritisation meeting.
Every Integration Is a Distributed System argued that boundaries fail first. An API boundary fails socially before it fails technically: the first fault is usually "we did not know you still read that that way."
Synchronous APIs make the social failure sharper. A caller waits for an answer that looks authoritative. When the meaning of that answer drifts, the caller embeds the drift into its own decisions immediately. The Hidden Cost of Synchronous APIs is partly about latency and coupling. It is also about how fast wrong certainty travels.
A mental model: the API as a published promise
Treat every public field as a published promise with four attributes:
- Claim: what business question the field answers in one sentence.
- Owner: a named role who can approve meaning changes.
- Audience: known consumer classes (internal services, partners, reports) and their migration capacity.
- Compatibility rule: what may change without notice, what needs a window, what requires a new version or a parallel field.
If any attribute is missing, the promise is informal. Informal promises erode under ordinary product pressure. That erosion is what teams experience as "the API won't stay stable," even when the OpenAPI hash barely moved.
Versioning fits this model as a compatibility tool, not a magic spell. /v2 helps when you are changing a promise on purpose. It does nothing when you keep /v1 and change what total includes. URL stability without meaning ownership is how you get reconciliations that never close.
How this shows up in modernisation
On legacy application modernisation work, the salvageable part of an old integration is often the transport and auth. The expensive part is reconstructing which fields were promises, which were accidents, and which partners built revenue on the accidents.
Teams that start by "rewriting the API cleanly" without naming owners of meaning and consumers usually recreate the same drift with prettier JSON. The rewrite succeeds on the network diagram and fails on month-end.
Stability work is less about freezing code and more about making change expensive in the right places: expensive to silently overload a money field, cheap to add an explicitly optional additive field with an owner and a notice path.
Implications for how you ship
Stop declaring "API complete" when Postman returns 200.
Declare it when:
- Meaning owners are named for fields that touch money, inventory, status, or customer promises.
- Consumer inventory exists somewhere that is not one engineer's head.
- Compatibility rules are written where a new hire can find them.
- Semantic changes have the same change process as schema breaks.
- There is a place to escalate when two consumers disagree about the same payload.
None of that requires a particular gateway vendor. It requires treating the contract as a product surface with backlog, owners, and blast-radius thinking.
Also stop treating unknown consumers as a non-problem. If you cannot list who will break, you cannot claim you did not break them. "No one told us they depended on that" is an inventory failure, not a consumer failure.
Stability theatre is common: freeze the path, bump a minor version for cosmetics, leave semantic ownership undiscussed. Partners learn not to trust the freeze. They cache aggressively, fork private mappings, and ignore deprecation mail. That behaviour looks like "bad consumers." It is rational adaptation to an unnamed social contract. The cure is not stricter scolding. It is making meaning changes as visible and gated as schema breaks, so trust can return.
One more trap: internal APIs get a pass because "we can just tell the other team." That works until the other team is three teams, or a contractor, or a report that nobody remembers is a consumer. Internal does not mean low blast radius. It means the pager is closer. Apply the same ownership tests, with shorter migration windows if you truly control both sides.
Questions before you call an API stable
Use this when a team wants to freeze a version, publish a partner feed, or claim "no breaking changes."
- Who owns the meaning of each field that can move money, stock, status, or a customer promise?
- Which consumers are known, and which are suspected but unlisted?
- What changes are additive, what are semantic breaks with the same types, and who decides?
- How long do consumers get to migrate, and who pays for the migration work?
- Where do two disagreeing consumers look when both claim the payload is "correct"?
- What is the notification path for a meaning change, and who is obligated to send it?
- If the producer database changes for an internal reason, what stays true on the wire?
If the answers collapse to "it's in the swagger," you have documentation. You do not yet have stability.
Fingerprint
Stable APIs are rare because stability was never the schema's job.
Schemas describe shapes. People own meaning, audiences, and compatibility. When those ownerships are unnamed, every rational producer change becomes someone else's unexplained break. The swagger stays green. The social contract does not.
The teams that keep APIs usable are not the ones with the strictest lint rules. They are the ones who treat every public field as a promise with an owner, a known audience, and a rule for how the promise is allowed to change.
APIs do not stay stable because files say so.
They stay stable when someone is accountable for what the bytes still mean after the business moves.
Related reading
- Data Contracts Matter More Than APIs
- Every Integration Is a Distributed System
- The Hidden Cost of Synchronous APIs
Architecture Files #28 · Part of Binary and Beyond. LinkedIn newsletter edition follows. Making API meaning and compatibility an owned product surface? Start a conversation.
