An OpenAPI pass is not a consumer contract
Teams shipping a BFF, a Shopify middleware, or a BuilderHelp admin API often treat a green OpenAPI lint — Spectral clean, examples valid, maybe a generated TypeScript client — as proof the integration surface is safe. That is schema hygiene. It is not a consumer contract. OpenAPI says the response shape is allowed. A consumer contract says a specific client still receives the interactions it depends on. We keep seeing the same gap: the spec passes, the React dashboard or Endeo sync path breaks on a renamed field nobody listed in the consumer's expectations.
Schema conformance and consumer expectation are different proofs
OpenAPI is a provider document. It describes routes, types, and optional nullability for anyone who might call the API. Tools that lint the file or replay examples against a running server check that the implementation still matches that document. Useful. Incomplete.
A consumer contract records what one caller actually issues and what it requires back — the path, the headers, the fields it reads, the matchers it tolerates. The dashboard may only need order id, print status, and a problem type. The mobile field app may need a different subset. Neither cares that the OpenAPI still allows fifteen unused properties.
When those proofs get collapsed into "the contract is green," people ship provider changes that stay schema-valid and still break a real client. The lint never saw the consumer's subset.
What a green OpenAPI miss looks like in production
A common cut: a field moves from required string to nullable, or a nested object becomes an array of objects, while the OpenAPI is updated in the same PR. Spectral passes. The generated client still compiles because the types widened. The kitchen-ticket worker that assumed a single address object throws at runtime.
Another cut: the provider removes a response field the OpenAPI never marked as required for all consumers — or never listed the consumer's private header convention. Schema tools report no break. The partner integration that keyed retry logic on that field starts double-submitting.
We have also seen the reverse comfort: teams skip consumer verification because "we generate clients from OpenAPI." Generation propagates the provider's story. It does not record that the print pipeline only ever read three fields and will not notice a breaking change until the first failed order after deploy.
Consumer contracts catch the subset that matters
Consumer-driven checks — Pact-style interactions or an equivalent recorded expectation — fail when the provider can no longer reproduce what a named client needs. The test names the consumer. The failure names which expectation broke. That is a different signal from "response does not match components.schemas.Order."
Use OpenAPI for the published surface: public docs, gateway validation, SDK generation, breaking-change diffs on the full schema. Layer consumer contracts on the seams you actually operate — admin UI to BFF, worker to internal API, widget to sync endpoint. Those are the places where a renamed field costs a real incident.
Nullable optional fields are where the two stories diverge most. The OpenAPI may allow null. A consumer interaction that never declared null will reject a perfectly legal schema response. Fix the consumer expectation or stop returning null — do not treat the OpenAPI as permission to ignore the client.
Where we put each check in a Fathom build
For APIs we own, the OpenAPI lives next to the server and runs Spectral plus a response-conformance check in CI. That keeps the published shape honest. For known consumers in the same program — a Next dashboard, a native client, a print worker — we add a small set of consumer interactions for the paths that move money, tickets, or inventory.
For APIs we only consume, OpenAPI (or the platform's versioned docs) is still the upgrade map. Consumer contracts on our side mean: our adapter tests encode the fields we read from Shopify, Stripe, or PrintNode, so a platform change fails our build before it fails the florist's Monday rush.
We do not invent a third handwritten "integration checklist" in a wiki. If the expectation matters, it is either in the OpenAPI diff gate or in a consumer verification that runs on the provider. Chat threads about "we think the mobile app still needs X" are not a contract.
What we check before calling the API surface done
OpenAPI lint and example validation are green for the routes we ship. Breaking-change detection runs on the schema, not only on prose in a changelog. At least one consumer verification exists for each high-stakes client we control.
Nullable and optional fields that real clients read have explicit expectations — not "whatever the generated type allows." Error shapes the client branches on are covered the same way we treat success bodies; an error string alone is still not enough.
The milestone is two proofs that disagree productively: the schema says what is allowed, the consumer contracts say what is still required. Until both exist, a green OpenAPI pass is documentation hygiene — not evidence the integration held.
Questions
- Is OpenAPI useless if we use consumer contracts?
- No. OpenAPI is the shared schema for docs, gateways, SDKs, and unknown future callers. Consumer contracts cover known clients' subsets. Use both; do not substitute one for the other.
- Do we need Pact specifically?
- No. You need recorded consumer expectations verified against the provider before deploy. Pact is one mature form. Homegrown fixtures that only mock the client side without provider verification are not a substitute.
- What if we only have one consumer?
- Still separate the proofs. One consumer's expectations will drift from a hand-edited OpenAPI. Verification keeps the server honest about that single client's reads — especially when the "one consumer" is a worker nobody opens in a browser.
Sources
- Pact vs OpenAPI for frontend–backend integration — OpenAPI validates schema; Pact verifies consumer-specific interactions — complementary, not substitutes.
- Consumer-driven API contract testing with Pact (2026) — Provider verifies recorded consumer expectations before deploy without both services live together.
- OpenAPI 3.1 contract testing guide — Spec-first conformance vs consumer-driven verification; when each fits.
- Related: An error string is not a contract