Versioning¶
Two numbers are published, and they say different things.
| Number | Where | What it names |
|---|---|---|
v1 |
The path, https://api.doc.cheap/v1/… |
The API: its endpoints, its authentication, its error shape |
1.0 |
meta.schema_version in every result |
The revision of the response body |
One response shape exists, and no way to ask for another. A request that tries
to select one is refused with 422
validation_failed naming what it sent. Silently
serving the only shape there is would leave the caller believing something else
arrived.
meta.schema_version¶
Every result carries it, and its value is "1.0".
It is a string, not a number: "1.0" and "1.10" are different revisions, and
arithmetic on them is wrong.
A consumer that pins the value fails loudly on a revision it was not written for, which is the point of publishing it. A consumer that reads the body without checking gets whatever the current revision means by each key.
What counts as breaking¶
A breaking change is one that can turn a working consumer into a broken one without the consumer changing.
| Change | Breaking |
|---|---|
| Removing a key from a response | Yes |
| Renaming a key | Yes |
| Narrowing a type — a nullable key that stops being nullable is not breaking; one that starts being nullable is | Yes, when it widens |
| Adding a value to an enum a consumer branches on | Yes |
| Removing a request field | Yes |
| Making an optional request field required | Yes |
| Changing what an existing key means, at the same type | Yes |
| Adding a key to a response | No |
| Adding an optional request field | No |
| Adding a new endpoint | No |
| Adding an error code to an endpoint that already answers errors | No |
| Widening an accepted range | No |
A removal or a rename moves the major segment of
meta.schema_version. An addition moves the minor segment.
What a consumer should assume¶
- Keys may be added. Ignore what you do not recognize rather than refusing the body.
fieldsis an open set. A key the field catalogue does not list is still published; render it fromlabelandcategoryrather than dropping it.- A string enum may gain a value. Branch on the values you know and have a
default;
meta.statusis the one to be careful with. - Null is a value. Every key is present, and a key whose value is unknown is
nullrather than absent.
A consumer built on those four assumptions survives every change in the non-breaking column without a release.
How a breaking change would be announced¶
Nothing here has broken yet — version 1 is the first production version, and this section describes what would happen rather than what has.
A breaking change to the response body would arrive as a new major
meta.schema_version. The previous revision would keep being served to callers
that ask for it, by a mechanism published with the change. A breaking change to
the API itself would arrive under a new path segment beside /v1, and /v1
would keep answering.
Either would be announced on the changelog, which carries an
Atom feed at /changelog/feed.xml, before the change
lands rather than with it.
An address, once published, keeps working. The per-code error pages are the
clearest case. docs_url in an error body is /errors/<code>, and a body
already sent cannot be rewritten, so those addresses do not move.
What is not versioned¶
request_idandevent_id. Both are opaque strings. Their shape is not part of the contract, and code that parses either is reading something that was never promised.- The scan id. A UUID version 7 in canonical form, and opaque past that.
- A
messagestring. It is for a person. Thecodebeside it is the stable half. - The recognition engine's own behaviour. A document that reads better next month reads better without a version moving; recognition quality is not a contract.