---
name: api-versioning
source: https://app.decimal.ai/s/api-versioning@1/SKILL.md
source_sha256: 5eb4418e5d15
---

# API URL versioning

## Contract

Version a resource-style HTTP API by a single integer major carried as the leading path
segment (`/v1/…`, `/v2/…`). A new major is minted only for a client-breaking change;
backward-compatible additions stay in the current major. The outgoing major keeps serving in
parallel through a deprecation window signaled by `Deprecation` and `Sunset` response headers,
and one version spans the whole API. Apply when designing or evolving an API's version scheme;
not for library release numbers, generic resource URL naming, or OpenAPI operation authoring.

## Rules

1. **Version in the path prefix.** The version is the first segment of every route:
   `/v2/orders`, `/v2/customers/17`. It is not a query parameter (`?version=2`, `?v=2`), not a
   header-only scheme, and not a segment buried after the resource (`/orders/v2`).

2. **One integer major, `v`-prefixed.** The token is the letter `v` followed by a single whole
   number: `v1`, `v2`, `v3`. No dotted form ever appears in the URL — never `/v2.1/`, never
   `/v1.4.0/`. Minor and patch numbers do not belong in the path.

3. **Bump the major only on a breaking change.** Increment (`v1` → `v2`) only when a change
   would break existing callers: removing or renaming a field, changing a field's type, making
   an optional input required, tightening validation, or changing the meaning of a status code.
   A new major is a rare, deliberate event.

4. **Additive changes keep the version.** A new endpoint, a new optional request field, a new
   optional query parameter, or a new field in a response is backward-compatible and ships
   inside the SAME major. You never mint a new major just because functionality was added.

5. **Never break a live version in place.** Once callers depend on a major, its contract is
   frozen against breaking edits. A breaking change ships as a NEW major served alongside the
   old one — you do not alter the responses the current major already returns.

6. **Parallel majors behind a deprecation window.** When the new major launches, the old one
   keeps serving. Mark the outgoing major with a `Deprecation` response header and give its
   shutdown date in a `Sunset` response header. Callers migrate during the window; only after it
   closes does the old major stop.

7. **Version the whole API as one unit.** Every endpoint shares one major and moves together —
   the whole surface goes from `/v1/…` to `/v2/…` at once. You do not version endpoint by
   endpoint (`/orders` on one number while `/customers` sits on another).

## Worked examples

Placement — the ad-hoc defaults, then the conforming route:

```
BEFORE   GET /orders?version=2          GET /orders/v2/items
AFTER    GET /v2/orders                 GET /v2/orders/items
```

Identifier — release number in the path vs. the integer major:

```
BEFORE   GET /v2.1.0/customers
AFTER    GET /v2/customers          (drop the minor and patch from the URL)
```

Additive change — needless bump vs. staying put:

```
BEFORE   added an optional discount field  →  published everything under a fresh major
AFTER    added an optional discount field  →  stays in the current major (it breaks no caller)
```

Breaking change — edit in place vs. a new major:

```
BEFORE   deleted the legacy_total field from the current major's invoice response
AFTER    left the current major untouched; the field is gone only in the new major served beside it
```

Deprecation — hard cutover vs. a signaled window:

```
BEFORE   switched the old major off the day the new one shipped
AFTER    old major keeps serving with
         Deprecation: true
         Sunset: Wed, 01 Oct 2025 00:00:00 GMT
```

Whole-API scope — per-endpoint drift vs. one cutover:

```
BEFORE   /customers/v2 while /orders is still on its first number
AFTER    one move: /v2/customers and /v2/orders together
```

## Edge cases & exceptions

- A brand-new field that is OPTIONAL with a sensible default is additive → same major.
- Making a previously-optional input REQUIRED breaks callers who omit it → new major.
- Changing an HTTP status code that clients branch on is breaking → new major.
- An internal rewrite whose observable responses are unchanged is not a versioning event.
- Two majors live at once is normal during a window; three or more live majors is a smell.
- Health, readiness, and schema endpoints (`/healthz`, `/openapi.json`) may sit outside the
  versioned prefix.
- The very first release is `v1`, not `v0` and not an unversioned path you retrofit later.

## Do / Don't

- Do put the major as the leading `/vN/` path segment. Don't pass it as `?version=` / `?v=`.
- Do keep the version to an integer major. Don't put minor or patch digits in the URL.
- Do reserve a new major for breaking changes. Don't cut one for an additive field or endpoint.
- Do serve the old major through a Sunset window. Don't break or delete a live version in place.
- Do version the whole API together. Don't let one endpoint run ahead of the rest.

## Common mistakes

- `/v1.2.3/` or `/v2.0/` — the full release number in the path; only the integer major belongs.
- `?api_version=2` / `?version=2` — the version as a query parameter instead of a path prefix.
- Minting a new major because an optional field or a new endpoint was added.
- Editing the current major's response shape in place and breaking existing integrators.
- Retiring the old major the moment the new one ships, with no `Deprecation`/`Sunset` headers.
- Per-endpoint versions that drift apart instead of one API-wide major.

## Quick checklist

- Version = leading `/vN/` path segment, integer major only.
- New major ⇔ a breaking change; additive changes stay in the current major.
- Old major kept live; retirement signaled by `Deprecation` + `Sunset` response headers.
- One major across the whole API, moved in a single cutover.
