Versioning
How Spec0 assigns and stores API versions — and the recommended policy for evolving an API without breaking consumers.
Spec0 keeps versioning (assigning a tag to a published spec) deliberately separate from breaking-change detection (deciding whether a change is compatible). Versioning is mechanical and always succeeds; detecting and acting on breaking changes is a policy you opt into. This page covers both.
How a version is assigned on publish
When you publish a spec, Spec0 resolves the version tag in this order:
- An explicit
--version <tag>you pass. - The
info.versionfield inside the OpenAPI spec. - A default of
0.1.0if neither is set.
Tags are arbitrary strings — semver (1.4.0), sha-prefixed (staging-abc1234), calendar (v2026-Q3), or anything else. Spec0 stores what you pass.
--semver: automatic patch bumps
Pass --semver and, if the API's previous version is a valid semver, Spec0 atomically increments the patch component under a lock (so concurrent publishes don't race):
latest = 1.4.0 → next published = 1.4.1If the previous version isn't valid semver, or this is the first publish, --semver is a no-op and the resolution order above applies (so a first publish lands at 0.1.0 unless you pass --version). --semver does no spec parsing and no diffing — it's purely mechanical.
spec0 publish ./openapi.yaml --name my-api --semver --visibility publishedVersions are immutable and unique
Once published, a version is immutable. A version tag is unique per API:
- Re-publishing the same spec content under an existing tag is idempotent — you get the existing version back, no error.
- Publishing different content under an existing tag returns 409 Conflict — bump the version instead.
Recommended policy: breaking changes start a new API
This is the convention Spec0 recommends — and dogfoods — but it is not enforced for you by the platform. You make it real in CI (below).
Keep every publish to an existing API backwards-compatible. When you need a breaking change, start a new API — typically my-api-v2 — and let consumers migrate on their own timeline. v2 lives alongside v1 for as long as both have users.
Why it's worth following:
- Consumers can't be surprised. If an API is still accepting publishes, pulling its latest version won't break existing integrations.
- Migration is opt-in. A v2 beside v1 means no coordinated cutover and no surprise SDK regenerations.
- Versioning stays boring. With breaking changes off the table for a given API, every publish is a non-breaking improvement.
For a major redesign: pick a new slug (payments-api-v2), publish it, and deprecate the old API when you're ready to encourage migration. The old API stays discoverable for as long as it has consumers.
Enforcing it in CI
You can gate on breaking changes in your pipeline as well as on the platform. spec0 diff --breaking-only exits non-zero when it finds a breaking change — wire that into PR checks:
# On every PR that touches the spec — fail if it breaks consumers
- run: spec0 diff origin/main:openapi.yaml ./openapi.yaml --breaking-only
# On merge to main — publish (idempotent if content is unchanged)
- run: spec0 publish ./openapi.yaml --name my-api --semver --visibility publishedSee the block-on-breaking-changes recipe for a complete workflow. Spec0 runs this exact policy on its own public API.
Breaking-change detection on the platform
Independently of the CI gate, Spec0 classifies changes server-side using oasdiff. There are two distinct mechanisms, and they behave differently:
1. Recording (always on). After a publish, Spec0 records the result on the version — a breakingChangesRecorded flag plus a summary — surfaced in the version history and via the public API. This never blocks or delays a publish.
2. The publish-time gate (opt-in). The breaking-change section of your org governance policy is evaluated during the publish, before the spec is stored. Its behaviour depends on the mode you set:
| Mode | Effect on a breaking publish |
|---|---|
OFF (default) | Publish succeeds. The change is still recorded. |
WARN | Publish succeeds, violation surfaced. |
BLOCK | Publish is rejected — HTTP 422, CLI exit code 7. |
So whether a breaking publish succeeds depends entirely on your org's configured mode. Out of the box the section is OFF, so publishes succeed and are simply marked — but an org that sets BLOCK gets server-side enforcement without any CI wiring.
The gate fails open: if the diff comparison can't run, the publish proceeds rather than being blocked by an infrastructure problem. Keep the CI check above as well — the two are complementary, not redundant.
What counts as breaking
This is what spec0 diff (and the server-side detector) classify — the same set the CI gate keys off:
| Compatible | Breaking |
|---|---|
| Adding a new endpoint or operation | Removing a field, parameter, endpoint, or operation |
| Adding a new optional field | Changing a field's type |
| Adding a value to an extensible enum | Adding a required field or required parameter |
Loosening a constraint (raising maxLength) | Tightening a constraint (lowering maxLength, narrowing a regex) |
| Adding a new optional query parameter | Removing or renaming an enum value |
| Updating descriptions, summaries, examples, server URLs | Changing authentication or required headers |
Changelogs
Every published version carries its own changelog — one entry per version tag, living on the version, not the API.
Right after a successful publish, Spec0 diffs the new spec against its predecessor and asks an LLM to summarise the change in plain English (routed through your own LLM key if configured, otherwise Spec0-hosted). By the time the version appears under Apis → <api> → History, the changelog is attached with an AI ✨ chip. If credits or AI aren't available, it falls back to a structural summary of the diff.
The owning team can edit the text inline (the chip drops — the entry is now manually owned), or hit Regenerate to re-run the generator against the same diff and overwrite it. There's exactly one entry per version; editing and regenerating both replace, with no history.
Read the same data from the CLI:
spec0 api changelog acme/orders --to 1.4.1 --output markdownSee spec0 api changelog for the full flag set.
See also
- APIs — the registry and how publishing works end-to-end.
spec0 publish—--versionand--semver.spec0 diff—--breaking-onlyfor the CI gate.- Governance — org-level linting and the policy roadmap.