How do I catch API schema drift before my tests miss it?
A test that checks the status code and a few fields keeps passing when the API adds a field, drops one you do not assert, or turns a number into a string. Nexus Studio stores the shape of each API response, field names and types and never values, compares it with the previous run and reports API_DRIFTED when it changes. With NEXUS_API_DRIFT_STRICT=true, the same change fails the suite.
By the Nexus Studio team, Verdict System LLC. October 27, 2026.
What is API schema drift?
Schema drift is the shape of a response changing between runs: a field added, a field removed, or a field whose type changed. An API is allowed to evolve. The problem is when its consumers find out in production, because nothing in the test suite said it moved.
Why does a passing contract test miss it?
Because it asserts only what its author thought of. A rule such as validate(res, { status: 200, hasFields: ['id'] }) keeps passing when price changes from a number to a string, since price is not in the rule. The client that adds up prices is the one that breaks.
Can I see it happen on a runnable example?
Save this as server.mjs. It answers one endpoint, and the SHAPE variable chooses what the endpoint returns: a price as a number, or a price as a string with a new currency field.
import http from 'node:http';
// SHAPE=v1 answers {id, price: 9.99}; SHAPE=v2 answers {id, price: "9.99", currency: "USD"}.
const v2 = process.env.SHAPE === 'v2';
http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(v2 ? { id: 7, price: '9.99', currency: 'USD' } : { id: 7, price: 9.99 }));
}).listen(4010, '127.0.0.1');
Save this API test as APIT900_drift_demo.api.js in the api-tests/rest folder of your workspace. It asserts the status and one field, like many real suites do.
// type: rest
import { runApiSuite } from '../../utils/base-api-fixture.js';
await runApiSuite([{
id: 'APIT900-1',
name: 'GET /product/7 returns 200 with an id',
fn: async ({ request, validate }) => {
const res = await request({ method: 'GET', url: '/product/7' });
validate(res, { status: 200, hasFields: ['id'] });
},
}]);
Start the server in one PowerShell window and run the test in another. The first run has nothing to compare with, so it records the shape and passes.
$env:SHAPE = "v1"; node server.mjs
$env:API_BASE_URL = "http://127.0.0.1:4010"; nexus api APIT900 --no-app
Stop the server, start it again with $env:SHAPE = "v2" and run the same test. It still passes, but the run is no longer API_SECURE. This is the output, shortened to the lines that matter:
schema drift: +1 field, 1 type change
+ currency
~ price: number → string
API_DRIFTED 7ms
1/1 passed
1 endpoint changed shape since the last run, set NEXUS_API_DRIFT_STRICT=true to fail the suite on it
Can drift fail the build?
Yes, on request. By default API_DRIFTED counts as a pass and exits 0, because the API moving is information and not a defect. It is printed on its own line and recorded with the difference in the evidence of the run, so it is never read as a clean pass. Set NEXUS_API_DRIFT_STRICT=true and the same change exits 1. To see it, run the test once against v1 and once against v2 with that variable set.
The baseline is always the previous run. After a drifted run, the new shape is the baseline, so the next run against the same response reports no drift.
What are the limits?
- The first run of a test only records a shape. There is nothing to drift from yet.
- The shape is names and types, not meaning. If
pricestays a number but changes from dollars to cents, nothing moves. - The comparison is with the previous run, not with a contract you agreed on. It tells you that something changed, not whether the change was allowed.
- Each test case keeps one shape, taken from the body of its last request. A list is described by its first element, an object by up to 200 keys, and nesting stops at six levels.
Where can I see it?
The verdicts guide covers the test verdicts, and the Evidence page lists the files a run leaves on your disk. The 30-day trial runs the same engine on your own API tests.