Track versions and deprecations
What each operation promises at its tier, and how long a deprecated one lasts.
/api/v1 is the only version you'll see in the URL. There's no date header, no ?version=, and no two parallel routes for the same thing. What this API versions is each operation's tier.
The three tiers
| Tier | What it promises | Where you see it |
|---|---|---|
estable | A deprecated operation keeps answering for 90 days, with Deprecation and Sunset headers from the first day onwards. It leaves estable only by being deprecated: it never drops back to beta. | In the Reference, next to the operation. |
beta | Published and usable, and it can change at any time. Every breaking change is written down in the changelog, and we email whoever called that operation in the last 30 days. | With the Beta label in the Reference. |
interna | Not part of the public API. It is not documented, it is not in the openapi.json we publish, and it can disappear without notice. | You do not see it: that is why it is not here. |
The tier travels inside the OpenAPI document itself, as the x-vitrina-tier extension.
No operation is estable today
Everything published is beta. Build on it anyway: beta doesn't mean unstable. It means notice of a change
reaches you through the changelog and by email, rather than
reaching you with 90 days on the clock.
The register of estable operations
An operation moves up with a date and a reason, and this table is that register. While it's empty, nothing has promised the 90 days. The promotion comes once an outside integrator depends on the operation, or once it has survived a whole stage unchanged.
| Date | Operations | Promoted by | Why |
|---|---|---|---|
| — | none yet; everything published is beta | — | — |
What happens when something is deprecated
An estable operation on its way out answers like this, from the day it is announced until its last one:
HTTP/1.1 200 OK
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMTSunset is the date it stops answering, 90 days later at the earliest. Deprecation says the clock has started. Both are standard headers (RFC 8594 and RFC 9745), so most HTTP clients already know how to read them. If your integration logs a warning when they show up, you'll find out without reading any email.
While that period runs the operation works exactly as before. It doesn't degrade, it doesn't answer more slowly, and it doesn't return fewer fields.
What counts as a breaking change
These break:
- removing an operation, a response field or an enum value;
- making a parameter required that was not;
- changing a field's type, or the meaning of one that already existed;
- changing the error code a case fails with.
These don't break, and can happen any day at any tier:
- adding an operation, an optional response field or a new enum value;
- adding an optional parameter;
- changing the text of
messagein an error.
For example, livemode was added to API keys and to every webhook event to tell a sandbox apart from a real workspace (Sandbox). It is one more optional field, so it doesn't break anything.
To see the difference between code and message, both of these failures come back from the same API:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing bearer token",
"requestId": "2bd4e229-842e-4e82-a0f2-4ff028c1e1fb"
}
}{
"error": {
"code": "FORBIDDEN",
"message": "Missing required scope: webhooks:write",
"requestId": "f7614421-ef08-4638-8524-2af320d1ab3c"
}
}code is the field you branch on, and changing it breaks your integration. message is written for whoever reads the logs, stays in English, and can be reworded any day (Errors).
Trap
A new field should not break you
We add fields to responses without notice, at every tier. A strict parser that rejects what it does not recognise falls over on that. Ignore the fields you did not expect instead of failing.
The first breaking change: the solicitudes:* scopes
POST /api-keys and POST /personal-tokens no longer accept
solicitudes:read or solicitudes:write in scopes[]. The feature those
permissions governed was retired from the product, so there is no replacement
scope to ask for instead. Credentials that already exist keep working; what
fails with a 400 is asking for those two values when minting a new one. Drop
them from the call and the rest of the scopes are minted exactly as before.
How you find out
- A change in
betais flagged as breaking in the changelog, generated from the spec. If it breaks, an email goes to whoever called that operation in the last 30 days. That list comes from each credential's real usage: if your key called the operation, you hear about it. - A change in
establearrives withDeprecationandSunsetin the response itself, the channel that doesn't depend on somebody reading the right email. - The
openapi.jsonwe publish is the same one the backend generates, so the most reliable way to know what changed is to diff it between two versions.