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_versionnewer than the server is refused:
{
"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.
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 ondetailtexts; - treat unknown enum values (a new call
status, a new eventtype) as "other".
Changes are listed in the changelog.
Deprecation policy
What we can change without notice, within /api/v1:
- add endpoints, optional request fields, response fields, headers, error
codevalues, webhook event types and enum values; - change the
detailtext 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 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 and
RFC 8594):
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):
| 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.
The scenario grammar, replay bundles and scoring follow their own rules, above: old scenario documents and bundles stay readable, they are upgraded when read.