# Versioning and compatibility

CallQA versions each contract separately, so one can evolve without breaking the others.

| Contract | Where you see it | Current |
|---|---|---|
| REST API | the path, `/api/v1` | `v1` (OpenAPI `info.version` `1.0.0`) |
| Scenario grammar | `schema_version` in every scenario | `1` |
| Replay bundle | `bundle_version` in every bundle | `1` |
| Scoring grid | `rubric_version` in every call result | e.g. `callqa-rubric-1` |

## Scenario grammar: `schema_version`

Every scenario document carries `schema_version`. If you omit it, the current version is
assumed. The rules the grammar follows:

- **Adding an optional field with a default is not a breaking change.** It happens within
  the same `schema_version`. Your existing documents stay valid and keep their meaning.
- **Anything else** (renaming, removing, changing the meaning or the type of a field) bumps
  `schema_version`. The server keeps an upgrade function for each old version.
- **Old documents are upgraded when they are read**, never rewritten in place. A scenario
  written for version 1 keeps loading after version 2 ships, and is returned in the latest form.
- A document with a `schema_version` **newer** than the server is refused:

```json
{
  "detail": "invalid scenario",
  "code": "invalid_scenario",
  "errors": [{ "loc": "schema_version", "msg": "schema_version 2 is newer than this server (1)" }]
}
```

Pin `schema_version` in the files you keep in Git: it documents which grammar they were written
for.

## Extension fields: `x_*`

Keys that start with `x_` are accepted anywhere in a scenario (top level, `target`, `persona`,
`limits`, steps, assertions, variants), stored and returned verbatim. CallQA never reads them.

```yaml
name: "Checkout flow"
target:
  phone: "+14155550100"
  x_region: "us-west"
persona:
  description: "Returning customer"
goal: "Buy the blue plan"
x_owner: "voice-team"
x_jira: "VOICE-142"
```

Use them for:

- your own metadata: owner team, ticket, cost center, environment;
- trying a field before it becomes part of the grammar.

Any other unknown key is rejected with `422`. This is on purpose: a typo such as
`must_includ` is caught at import instead of silently ignored.

An `x_` field will never be taken by the grammar: new official fields never start with `x_`.

## Replay bundles: `bundle_version`

Bundles are versioned independently. The server refuses a bundle whose `bundle_version` is
newer than the one it supports. Keep old bundles: they stay importable.

## Scores: `rubric_version`

A score is computed from verdicts by a versioned scoring grid. Any change to the weights or the
rules ships as a new `rubric_version`. Compare scores only within the same rubric version; the
regression dashboard shows when it changes.

## REST API

The API version is part of the path (`/api/v1`). Write tolerant clients:

- ignore response fields you do not know: new fields can be added to responses;
- do not depend on the order of keys in JSON objects;
- match errors on the status and `code`, not on `detail` texts;
- treat unknown enum values (a new call `status`, a new event `type`) as "other".

Changes are listed in the [changelog](changelog.md).

## Deprecation policy

What we can change without notice, within `/api/v1`:

- add endpoints, optional request fields, response fields, headers, error `code` values,
  webhook event types and enum values;
- change the `detail` text of errors, the order of JSON keys, and the performance or the
  limits within the documented ranges.

Anything else is a breaking change (removing or renaming an endpoint or a field, changing its
type or meaning, making an optional field required, tightening validation). A breaking change
is never made in place:

**Announcement.** The change is announced in the [changelog](changelog.md) and by email to
the admins of every organization that used the endpoint in the last 90 days.

**Deprecation period.** The old behavior keeps working for at least **6 months** for an
endpoint or a field, and at least **12 months** for a whole API version (`/api/v1` after
`/api/v2` ships). Security fixes are the only exception: if keeping an endpoint would put
your data at risk, the notice can be shorter, and the changelog says why.

**Signals in every response.** Deprecated endpoints are marked `deprecated: true` in the
OpenAPI document and answer with these headers
([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745) and
[RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)):

```
Deprecation: @1768435200
Sunset: Thu, 15 Jul 2027 00:00:00 GMT
Link: <https://app.callqa.dev/docs/changelog/>; rel="deprecation"
```

`Deprecation` is the date (Unix seconds) the endpoint was deprecated, `Sunset` the date it
stops working, and `Link` the page that explains what to use instead.

**Removal.** After the `Sunset` date the endpoint answers `410` with
`{"code": "endpoint_sunset"}`.

Watch for the `Deprecation` header in your client and log a warning when it is present: your
CI logs then tell you months ahead.

Deprecated today (sunset on 2027-03-31, see the
[changelog](changelog.md#versions-replace-releases)):

| Deprecated | Use instead |
|---|---|
| `/releases` and `/releases/{id}` (every method), `POST /releases/{id}/promote` | `/versions`, `POST /versions/{id}/promote` |
| `release` in the body of `POST /scenarios/{id}/runs` | `version` |
| `release` in run responses | `version` |
| `a_release` / `b_release` on `GET /environments/compare` | `a_version` / `b_version` |
| Webhook events `release.created`, `release.promoted` | `version.created`, `version.promoted` |

The `/releases` routes answer with the `Deprecation`, `Sunset` and `Link` headers; deprecated
fields and events are marked in the [API reference](api-reference.md).

The scenario grammar, replay bundles and scoring follow their own rules, above: old scenario
documents and bundles stay readable, they are upgraded when read.
