Versioning
What the version in the path promises, what we change without touching it, how a breaking change ships, and the one narrow case where the current version is corrected in place instead. The prefix, the version and the policy figures are read from the API's own configuration.
- /v1Path prefix
- 1.1.0Spec version
- 133Operations under it
- 0Deprecated operations
How versions work
Every endpoint lives under /v1. That prefix is the contract: while it answers, a request that worked keeps working. The OpenAPI document's info.version (1.1.0) says which release of that contract is live, and its major number is the prefix.
# The prefix is the contractcurl -H "X-API-Key: your_api_key" https://api.gunspec.io/v1/firearms/glock-17-gen5 # The document says which release of that contract is livecurl https://api.gunspec.io/openapi.json | jq .info.version# "1.1.0"- 1Major: the prefix
- Moves only with a breaking change, and then the path moves with it:
/v1becomes/v2. Nothing under the old prefix changes shape on that day. - 1Minor: additive
- Moves when something is added: an endpoint, a field, a parameter, an enum value, an event. A client that ignores what it does not know sees no difference.
- 0Patch: fixes
- Moves for a correction that changes no shape: a wrong description, a figure that was off, a validation that was looser than documented.
2 paths sit outside the prefix because they describe the service rather than the data, and are stable by design: /health, /ready. The version field on a catalog record is unrelated: it is a fingerprint of that record's data for caching and mirroring, described under Data revisions and in the field reference, and it changes when the record does, not when the API does.
What changes without a new version
Additive changes land under /v1 as they are ready, with a minor bump to info.version and a changelog entry. A client written against the rules below never notices them.
- New endpoints, and new optional query parameters on existing ones. An unknown parameter is ignored, never refused.
- New fields on a response. They arrive alongside the ones you read; nothing you read moves or changes type.
- New values in a closed vocabulary. A record can carry a status, an action type or an event name that did not exist when your code was written.
- New
error.reasonvalues under an existingerror.code. The code is the family your code branches on; the reason is the specific situation, and the list grows. - More data. Records gain fields that were null, counts go up, lists get longer. A figure that was null becoming a value is not a change of shape.
Vocabularies that grow
firearm.status6 todayA record's production status. Match the values you handle and treat the rest as unknown, never as an error.
in_productiondiscontinuedout_of_production- all 6
firearm.actionType46 todayThe mechanism a firearm uses. The longest of the four, and the one most often switched over exhaustively.
short_recoilbolt_actionblowback- all 46
X-Webhook-Event18 todayWhat a delivery is about. A subscriber to a wildcard receives new event names as they ship.
firearm.createdfirearm.updatedfirearm.deleted- all 18
error.reason39 todayWhy a request was refused, one level under the status. Branch on the code first, then on the reasons you can act on.
INVALID_PARAMETERINVALID_JSONINVALID_REQUEST- all 39
What counts as breaking
A breaking change is one that makes a request that used to work stop working, or return something a correct client would misread. It ships under a new prefix; /v1 keeps answering as it did.
- Removing or renaming a field, an endpoint, a parameter or a vocabulary value.
- Changing a field's type or unit, or the meaning of a value that already exists.
- Tightening validation so that a request that used to be accepted is refused.
- Changing which status code, error code or plan a request answers with.
- Changing the envelope, the authentication headers or the pagination contract.
The default path
- 1The change is filed as a
breakingentry in the changelog before it ships, stating what moves and why. - 2The new contract goes live under
/v2. The spec's major moves with it, and the SDKs gain a release that targets it. - 3
/v1keeps answering unchanged for at least 12 months from that day. The operations it will lose are markeddeprecatedin the spec, which the reference and the SDKs surface. - 4When the old prefix is retired, the date is announced in the changelog at least 12 months ahead, and every key that called it inside the usage window is contacted directly.
The exception: correcting v1 in place
A new prefix is the right answer for a change of mind. It is the wrong answer for a defect: keeping /v1 wrong for 12 months so that nobody's code changes serves the few callers who depend on the wrong answer and fails everyone else. Where the usage logs show the affected surface has almost no consumers, the fix may land in the current prefix instead. The conditions are narrow and all of them must hold.
When it is allowed
- The current behaviour is wrong against its own documentation, not merely inconvenient: a figure in the wrong unit, a status that lies, a validation that lets through what the spec forbids.
- The usage logs for the last 90 days show the affected endpoint, field or value is consumed by almost nobody, and every key that did call it can be contacted directly.
- The corrected behaviour is what a reader of the documentation already expects, so a client written to the docs works better after the change, not worse.
- It is a fix, not a feature: nothing new is introduced, and the change is the smallest that makes the behaviour true.
When it is not
- Any endpoint the SDKs, the website or a published integration pack calls in a documented flow.
- Any change of a field's name, type or unit on a record: a mirror holding the old shape cannot tell a corrected value from a changed one.
- Any change to authentication, the envelope, the error contract or pagination.
- Anything where a consumer would be surprised rather than relieved.
How it ships
- 1A
breakingentry lands in the changelog and the feed at least 30 days before the change takes effect, naming the endpoint, the old behaviour, the new one and the date. - 2Every key that called the affected surface inside the last 90 days receives the same notice by email, so nobody learns of it from a failing request.
- 3The change lands on the announced date with a patch to
info.versionand a matching SDK release; the changelog entry is updated to say it has shipped. - 4Where a caller cannot move in time, the old behaviour is kept for them behind a per-key exception until they can, so an announced correction never breaks a customer who asked for more time.
Breaking entries published so far (1)
- Five answers changed - check them against your integration
Most of this release adds information. These five change an answer your code might already branch on.
Deprecated operations
An operation on its way out is marked deprecated in the OpenAPI document, which generated clients and the reference surface. It keeps answering until the retirement date its changelog entry names.
Nothing is deprecated in 1.1.0. Every documented operation is current.
Where to watch
Four places a change is announced. Following one is enough; a breaking change appears in all of them.
The changelog
Every change, filed by category. Filter on breaking to see only what needs your attention.
The feed
https://api.gunspec.io/changelog.xml carries the same entries the moment they publish, for a feed reader or a CI job that fails a build on a new breaking entry.
The OpenAPI document
https://api.gunspec.io/openapi.json carries info.version and every deprecated flag. Diffing it in CI against the copy you built from is the mechanical check; see tools for the Postman and Swagger routes to it.
SDK releases
A new prefix is a major release of the SDKs, an additive change a minor one, so pinning the SDK's major pins the API's.
Writing a client that survives
Five habits that make every compatible change invisible and every breaking change a planned upgrade.
- Pin the prefix. Build
/v1into the base URL, never into a setting a deploy can change by accident. - Ignore what you do not know. Unknown fields, unknown parameters, unknown vocabulary values and unknown
error.reasons are all normal; treat them as absent, not as errors. - Branch on
error.code, then on the reasons you can act on. The error reference lists both. - Page from the page you received, not from a total you may not have. The pagination page has the loop.
- Use an SDK where you can. Its major tracks the prefix, its types track the spec, and its auto-paging and retry already follow these rules.