Versioning and Stability
What the v1 contract guarantees, what counts as a breaking change, the six-month notice period, and what your client has to do to stay covered.
The contract is the OpenAPI 3.1 document served at:
https://app.vitalsentinel.com/api/public/v1/openapi.jsonNothing outside that document is promised. If a field is not in the schema, do not depend on it.
What the contract covers
- Every path and HTTP method under
/api/public/v1 - Request parameters and bodies, including which are required
- Response schemas, including field names, types, and nullability
- The
typeURI on every error, and the HTTP status that accompanies it - Scope strings, and which scope each operation requires
- The pagination envelope, the cursor parameter, and the time-range parameters
What it deliberately does not cover
- The prose in an error's
detailfield. Written to be acted on, and reworded whenever a better wording exists. Branch ontype. - The exact value of a cursor. Opaque by design.
- Ordering of results where an endpoint does not document one.
- Response timing, or whether a given answer came from cache.
- Which endpoints sit on the
heavy-queryrate-limit bucket. That is an operational tuning decision, not a contract term. - Credit prices for a given route. They follow the cost of the work, and the cost of the work can change.
X-Credits-Coston the response is always authoritative.
What counts as a breaking change
Breaking, and requires /api/public/v2 plus the notice period below:
- Removing an endpoint, a field, or a scope
- Renaming anything
- Making an optional request parameter required
- Narrowing a type, or making a nullable field non-nullable
- Changing which scope an operation requires
- Changing the HTTP status or
typeURI for an existing error condition
Removing a field has one narrow exception. When the upstream source of a value disappears, the field goes inside v1 rather than waiting for the next major version, because a field that can only ever report a wrong answer is worse than an absent one. That is the only condition. A field that merely became unfashionable stays.
Additive, and ships inside v1 at any time with a changelog entry:
- A new endpoint
- A new response field
- A new optional query parameter
- A new enum member
- A new scope
- A new error
typefor a condition that previously had no specific one
The commitment
A breaking change requires a new major version in the path, and the version being retired stays available for a minimum of six months after its successor is generally available.
Six months is a floor, not a target. The clock starts when v2 can actually be called, not when it is announced, because you cannot migrate against a version that does not exist yet.
Individual operations are not deprecated inside a major version. If an operation has to go, it goes in the next major version, on the same schedule as everything else in it. A surface where any single endpoint can vanish on its own timetable is one nobody can plan around.
How a retirement is signaled
| Channel | What it carries |
|---|---|
Response header Deprecation | The instant the deprecation takes effect, as an RFC 9745 structured-field Date: Deprecation: @1794355200 |
Response header Sunset | The instant the operation stops being served, as an HTTP-date: Sunset: Tue, 11 May 2027 23:59:59 GMT |
Response header Link with rel="deprecation" | A documentation URL for that specific retirement |
| Release notes | The authoritative record of what changed and what to move to |
The two date headers are deliberately different shapes, and that is not a bug: RFC 9745 defines Deprecation as a structured-field Date, while RFC 8594 predates structured fields and defines Sunset as an HTTP-date.
Nothing in v1 is deprecated today, so no response currently carries any of these three headers. The mechanism ships live and dormant, which means you can build against it now and it will work when it is first used.
Detecting a change yourself
Two mechanisms are worth wiring into CI:
Poll and diff the OpenAPI document. It is unauthenticated, deterministic for a given deployed state, and its paths and operations are sorted, so a diff shows content changes rather than reordering noise.
curl -sS https://app.vitalsentinel.com/api/public/v1/openapi.json \
| jq -S . > openapi.new.json
diff -u openapi.json openapi.new.jsonWatch info.version. It moves only when the published contract moves.
On our side, a committed snapshot of that same document is checked by an automated test, so a schema change to a live endpoint cannot ship without someone deliberately updating the snapshot in the same pull request. That is a review-time gate: it guarantees a change is noticed, not that it is automatically classified.
What your client must do to stay covered
The guarantees above are written against clients that follow five rules. A client that does not follow them can be broken by a change this policy calls additive:
- Ignore unknown fields. A new response field is additive and will appear without warning. A client that rejects unknown keys will break on it.
- Tolerate unknown enum values. Treat an unrecognized value as "something new", not as a fatal error.
- Branch on the error
typeURI, never on the prose indetailor on the status code alone. Two different403s need two different responses from your code. - Treat cursors as opaque. Do not parse, construct, or persist one across a deployment.
- Honor
Retry-After. A client that ignores it is indistinguishable from one that is attacking us.
Scope stability
Scope strings are part of the contract.
- Adding a scope is additive.
- Removing one, or changing which scope an operation requires, is breaking.
- Removed scope strings never come back. They are permanently blocked, so a future endpoint cannot be quietly authorized by resurrecting a string that an explicit decision removed. See Scopes that do not exist.
Kill switches are not deprecations
A 503 carrying api-disabled or read-only-mode is an operational lever, not a contract change. Retry after the interval in Retry-After. Neither means anything is going away.