Most data APIs treat their response shape as a convention. It lives in the docs, it lives in a TypeScript type someone hand-maintains, and it lives — really lives — in the code that produces it. Three copies, none authoritative.
That arrangement works until someone renames a field. Then you find out from a consumer, usually at the worst possible moment, and usually by way of a null that used to be a string.
The contract as a file
At BatScrape the wire contract is a package. It has a version, it is vendored into every actor repository, and nothing ships without it.
packages/contract/ source of truth for the wire
vectors/wire-v1.json golden signature and schema vectors, append-only
scripts/verify-vectors.mjs the gate
The vectors file is append-only, which is the constraint that makes the rest work. You cannot quietly change what v1 means; you can only add v2. A rename is now a diff someone reviews, not a discovery someone makes.
Where the gate runs
This is the part that took us a second attempt to get right.
The obvious place for a contract check is CI. We tried that, and measured it three times on the previous project: the platform build finished 80 to 83 seconds before CI had an opinion. A red tick sat on a commit whose artefact was already tagged and pullable.
So the gate moved inside the Docker build, between install and build:
COPY packages/contract ./packages/contract
COPY vectors ./vectors
RUN node scripts/verify-vectors.mjs
RUN pnpm run build
If the wire moved, the image does not exist. There is no race to lose.
What it costs
Two seconds per build, and the discipline of writing a vector before writing a field. That is genuinely all. The thing it buys is that “the API changed” stops being a category of incident.
What it does not solve
A contract cannot tell you the source was lying. If a site starts returning prices in a different currency without changing the field, the wire is unmoved and the data is wrong. That is a different problem, and it belongs to the actor, not the contract.
Dana Reyes
Staff Engineer