Chapter 05 of 09
Contract Drift: Monitoring Correctness, Not Just Availability
Why a 200 proves nothing about the body behind it, which contract a live response should be validated against, and how a silent deviation becomes a record with an owner.
Uptime monitoring asks whether your API answered. It has no opinion at all about what the answer contained, which is why a response can be wrong in a way that breaks every consumer while every dashboard stays green.
The silent failure
A deploy renames tracking_number to trackingNumber. The endpoint still returns 200, in the same time it always did, with a body that is still valid JSON.
Uptime is unaffected. Latency is unaffected. Every consumer that reads that field starts getting undefined, and the ones that validate strictly start throwing. The failure is total and completely invisible to a check that only reads the status line.
That span is the thing worth measuring. It opens at a deploy, and it closes either when somebody updates the contract or when somebody updates the service, and the interesting number is how long it took anyone to find out. The reasons these gaps open in the first place are the subject of why OpenAPI specs drift.
What counts as a deviation
Not every mismatch deserves the same reaction, and treating them alike produces either noise or blindness.
| Deviation | Severity | Why |
|---|---|---|
| A required field is missing | Breaking | Every consumer that reads it fails |
| A field changed type | Breaking | Deserialisation fails, often loudly and far downstream |
| An enum returned a value the contract does not list | Breaking for strict consumers | Anything switching on the value falls through |
| A format no longer matches | Depends | A date that stopped being ISO 8601 breaks parsers, while a looser pattern usually does not |
| An undocumented field appeared | Informational | Most consumers ignore it, and the strict ones do not |
A workable default is to record everything and alert on the breaking classes only. The informational ones still accumulate in the record, so the answer to "how long has that field been arriving" exists when somebody finally asks.
Which contract you are checking against
This is the decision that determines whether the whole exercise works, and there are two defensible answers and one wrong one.
| Baseline | When it is right |
|---|---|
| The version this environment is pinned to serve | Almost always, because it is what the environment promised |
| The latest published version | When nothing is pinned, as a fallback |
| The newest draft | Never, because production is not supposed to serve your work in progress |
The failure mode worth watching for is a pin that has fallen behind. Production now serves 2.1 while the pin still says 2.0, so the monitor validates against the old contract and drift against the newer one stays invisible. A monitoring setup that cannot tell you which contract it is comparing against is not really comparing anything, so every drift record should name its baseline rather than assuming one.
Warn or strict
Once you detect drift, you have to decide what it does to the check that found it, and the two options encode genuinely different positions.
Warn records the deviation and leaves the check successful. The reasoning is that the endpoint answered exactly as it was supposed to on the wire, so failing it would push a correctness problem into an availability number that customers read.
Strict fails the check on breaking drift. The reasoning is that a response nobody can parse is not a successful response, whatever the status line says.
Warn is the better default, and strict is the right answer for an endpoint whose consumers would call a shape change an outage. The thing to avoid is picking strict everywhere and then explaining an uptime figure that mixes two different kinds of failure.
Drift is not an incident
A drift finding and an outage want different handling, and collapsing them into one workflow damages both.
An outage has a clear end, so its record wants a duration and a resolution. Drift has a start and an owner, and it may legitimately stay open for a week while the team decides whether the contract or the service is the thing that is wrong. Two properties make that record trustworthy.
The first-seen date never moves. Somebody closing and reopening an item should not reset how long it has been going on.
The observation count is recorded rather than scored. A deviation seen on sixty-nine checks across two days is a deploy, while one seen once is a blip, and the raw count says that better than a confidence percentage nobody can audit.
In Routebase
Every monitor check that gets a response validates that response against the contract its environment is pinned to, falling back to the latest published version where nothing is pinned. A deviation that keeps appearing becomes a drift event with a first-seen date, an observation count and a lifecycle you acknowledge and close, and it resolves itself when a later check matches the contract again.

The detail names the field path, the change type, what the contract expected and what actually arrived, and a field the response did not carry at all reads as not present rather than as null. The failing request and response are kept with it, and credentials never reach that record, because secret variables are not substituted into the stored copy in the first place rather than being filtered out afterwards.
Watch this API for drift sets all of it up for a whole specification in one environment from a single dialog. Disabling it later turns validation off while leaving the monitors running as uptime checks. See Contract Drift and Schema Drift, and the product page for API monitoring for how drift events, alert policies and incidents fit together. The same question asked before a release instead of after it is contract testing.
Frequently asked questions
What is API contract drift?
Contract drift is the gap between what your specification promises and what your service actually returns. It opens the moment a deploy changes a response without the contract being updated, and it stays open until somebody notices. Nothing is down while it lasts, every status code is a 200, and the first sign is usually a consumer whose parser broke.
How do you detect contract drift in production?
Validate the body of every monitoring check against the contract the environment is supposed to be serving, rather than only checking the status code. Because the response was already fetched for the uptime check, the validation costs configuration rather than traffic. A deviation that keeps appearing then becomes a record with a first-seen date and an observation count, which is what turns a silent mismatch into something with an owner.
Should contract drift fail the check?
By default it should not, because the endpoint answered correctly on the wire and counting that against uptime hollows out a number you publish to customers. Recording the drift and alerting on it is enough for most teams. Strict mode, where breaking drift fails the check outright, is the right choice when a contract violation genuinely is an outage for the consumers of that endpoint.
Which version should a monitor validate against?
The version the environment is supposed to be serving, which means the version it is pinned to, falling back to the latest published version where nothing is pinned. Validating against the newest draft reports drift every time somebody edits a schema, which is noise rather than signal. The failure mode to watch for is a pin that fell behind production, because the monitor then keeps checking a contract nobody is serving any more.
Last reviewed by The Routebase Team.