All posts
API Testingconsumer-driven contractsPactAPI contractsbreaking changesmicroservicesPR checks

Consumer-Driven Contracts, Without the Contracts

The most expensive API breakage isn't a bug inside one service — it's one team changing a response field another team quietly depends on. QuantumVerifi derives what each consumer actually reads from its code (no hand-written Pact), then proves whether a provider change still satisfies it.

QuantumVerifi Team28 June 20265 min read

Your unit tests pass. Their unit tests pass. Production breaks anyway.

This is the most common — and most expensive — way real systems fail, and almost nothing tests for it. It isn't a bug inside a service. It's the seam between two services: one team changes an API response, and another team that quietly depended on the old shape finds out from a 2am page.

QuantumVerifi now tests that seam.

The break nobody catches#

A worked example. Acme runs two things in two repos with two teams:

  • the Orders API (backend), and
  • the Checkout app (frontend), which calls Orders and reads order.id, order.status, and order.total off the responses.

The Orders team has no real idea Checkout depends on order.status — that fact lives buried in Checkout's code, in another repo, owned by other people.

One day a backend engineer opens a tidy PR: rename statusstate, drop the unused legacy_total field. The Orders team's tests are green. It looks completely safe. It ships.

Checkout breaks in production. The integration was never tested, because each side only tests itself.

Why existing approaches miss it#

Schema/provider-side testing checks that the Orders API matches its own OpenAPI spec. Useful — but it has no idea what Checkout actually uses, so it can't tell you the rename is breaking.

Pact-style consumer contracts were built for exactly this — but they require every consumer team to hand-write and maintain a contract file describing their expectations. In practice those files drift, go stale, or never get written. The contract is only as good as the discipline behind it.

QuantumVerifi takes the Pact insight — let the consumer define the contract — and removes the hand-authoring.

The crux: what the consumer reads#

Here's the key idea. A provider can safely delete a field nobody reads. It breaks someone only when it deletes a field someone reads.

So the contract that matters isn't the whole response schema — it's the specific set of response fields each consumer's code actually touches. We call this the consumer's response_depends_on.

QuantumVerifi derives it by reading the consumer's source directly:

TypeScript
// Checkout's code const order = await api.get(`/orders/${id}`); renderStatus(order.data.status); // ← depends on: order.status total = order.data.total; // ← depends on: order.total

From this, with no hand-written contract, QuantumVerifi infers:

Checkout → Orders API: GET /orders/:id must return status and total.

Now the Orders team's "rename statusstate" PR can be judged against a fact nobody had to maintain: Checkout reads status. Removing it is a breaking change. Dropping legacy_total, on the other hand, is provably safe — no consumer reads it — so it sails through. That precision is the whole point: it flags real breaks and stays quiet on safe changes, so teams trust it instead of muting it.

Deriving contracts from real code#

The hard part is reading code the way real apps are actually written. A naïve scan that only understands axios.get('/path') finds almost nothing — because real apps configure a client once and reuse it everywhere:

TypeScript
// one file, written once const api = axios.create({ baseURL: process.env.ORDERS_URL }); // a dozen other files const order = await api.get(`/orders/${id}`);

We validated the extractor against real open-source TypeScript codebases, not just clean samples — and that's exactly where the early version fell down. So the derivation resolves the configured client across files, follows the response variable to the fields that get read, and bounds that analysis to the calling function so unrelated reads never leak in. On a real-world app, that's the difference between recovering a couple of incidental calls and recovering the app's actual API surface — POST /transactions, GET /checkAuth, PATCH /users/:id, and the fields each one depends on.

Where a call is genuinely ambiguous — a dynamically built URL, an indirection we can't follow deterministically — QuantumVerifi doesn't guess. A low-confidence dependency is surfaced as a warning, never a hard failure. We never fail a provider's change on a field we only think you depend on.

Verifying a change — including one that isn't deployed yet#

Knowing the contract is half the job. The other half: does the provider still satisfy it?

QuantumVerifi checks the contract two ways:

  • Against the live provider — probe it and observe the actual response shape.
  • Against a change that hasn't shipped — derive the provider's behavior from the OpenAPI spec in the pull request. This is what lets the check run on the PR, before the change is ever deployed: compare "what Checkout needs" to "what this PR's provider would return."

The result is a verdict, per consumer, per operation: satisfied, broken (a high-confidence dependency is gone), or warning (a low-confidence one). Each verdict is sealed with a tamper-evident hash and is secret-free — it records field names only, never a single value, token, or credential.

The product moment#

Put it together and you get the workflow that closes the loop:

The Orders engineer opens the rename PR. A check appears next to their unit tests: ❌ Breaking change — Checkout depends on order.status, which this PR removes (checkout/src/order.ts:88). ✓ legacy_total — safe to remove, no consumer reads it.

Named consumer. Exact field. Exact line. Caught before merge — not in an incident.

That's consumer-driven contracts with the maintenance burden removed: the contract is derived from the code that defines it, kept honest by confidence, sealed for audit, and aimed at the one seam traditional testing leaves uncovered.

It's the same QuantumVerifi promise — is this safe to ship?, answered with proof — pointed at the gap between your services.