# Versioning and breaking changes

Source: https://docs.usetone.ai/versioning

> /v1 is a promise, not a version number. What that commits us to, what it asks of you, and how it is enforced.

There is no plan for a `/v2`.

That is not optimism — it is a constraint we accepted, and it shapes how the API
is designed. Anything we cannot do additively, we do not do.

## What we promise

**Additive only.** These can appear at any time and are **not** breaking:

- A new endpoint
- A new **optional** request field
- A new response field
- A new value in any enum-shaped field
- A new webhook event type
- A clearer error message

**Never, on `/v1`:**

- Removing or renaming an endpoint, a field, or an error code
- Changing a field's type
- Making an optional request field required
- Removing a value we have previously published
- Changing an endpoint's success status code
- Requiring a broader scope than before

## What that asks of you

One thing, and it is the whole bargain.

🔴 **Treat every enum as an open set.** A field documenting `answered`,
`no_answer`, `busy`, `voicemail`, `failed`, `unknown` is telling you what exists
today — not what can ever arrive. If your client throws on an unrecognised
value, our compatible change becomes your outage.

```js
// Wrong — turns our additive change into your incident
switch (call.disposition) {
  case 'answered': return handleAnswered(call);
  case 'busy':     return handleBusy(call);
  default:         throw new Error(`unknown disposition: ${call.disposition}`);
}

// Right
switch (call.disposition) {
  case 'answered': return handleAnswered(call);
  case 'busy':     return handleBusy(call);
  default:         return handleUnknown(call);   // log it, carry on
}
```

The same applies to `error.code`: branch on the codes you handle, and fall back
on `error.type` and the HTTP status for one you have not seen.

## How this is enforced

Not by review. `backend/openapi.public.json` is committed, and every change to
the API is diffed against it:

```bash
pnpm --filter backend openapi:check   # the spec matches the code
pnpm --filter backend openapi:gate    # the change is allowed on /v1
```

The gate classifies every difference and **fails the build** on a removed
operation, a removed or retyped field, a narrowed set of known values, a newly
required field, a moved success status or a widened scope. A breaking change
cannot be merged by someone who did not notice it was breaking.

## Deprecation

When something is superseded we mark it deprecated, keep it working, and say
what to use instead. We do not set removal dates for `/v1`, because a removal
date is a breaking change with a delay on it.

A voice retired by our speech provider is the clearest case: it disappears from
the pickers, keeps working for every agent already using it, and is flagged
`deprecated` in the catalog. A vendor's documentation edit must not break a
tenant that changed nothing.

## Date-pinned versions

Some APIs let you pin a version by date. We have deliberately not built that.

A pinning mechanism makes breaking changes *possible*, and a thing that is
possible gets justified. The constraint is more valuable than the escape hatch —
so if we ever need one, that will be a considered decision with a reason, not a
header that was already there.
