# API versioning

There is no version in any URL and no version header to send. The published descriptions carry the product version, each protocol carries its own revision, and compatibility is handled at the protocol layer — the MCP endpoint accepts four revisions and the earlier agent-card path still answers.

- **Status:** Available
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/developers/api-versioning/

## Where a version appears

| Artefact | Field | Meaning |
|---|---|---|
| `/developers/openapi.yaml` | `info.version` | The product version at the time the description was generated |
| `/developers/workflows.arazzo.yaml` | `info.version` | The same product version; the Arazzo spec version is `1.1.0` |
| `/.well-known/agent-card.json` | `version` | The product version; `protocolVersion` is the A2A revision, `1.0.0` |
| `POST /mcp` | `serverInfo.version` | The corpus version reported by the server |
| `POST /mcp` | `protocolVersion` | The MCP revision agreed for the call — `2026-07-28` by default |
| `/api/public/docs/status` | `version`, `generated` | The corpus version and the date it was built |

Two different kinds of version live side by side and confusing them is the usual mistake. The *product* version moves when Connect ships. The *protocol* version is the specification revision an interface implements and moves when that specification does. A card can carry a new product version and the same `protocolVersion: 1.0.0` for a long time, and nothing is wrong.

## How compatibility is actually kept

Not by a version selector, which nothing here has, but by accepting more than one revision where a specification has moved. The MCP endpoint implements `2026-07-28` and also answers clients speaking `2025-11-25`, `2025-06-18` and `2025-03-26`: an `initialize` from one of those gets a normal result with the agreed version echoed back, and later requests are accepted without the per-request metadata the current revision expects. Refusing them would have meant the connector directories of the day could not reach Connect at all.

The same instinct applies to the A2A card. An earlier draft of the specification put it at `/.well-known/agent.json`; that path still answers with the same document plus a `Link` header naming the current path as canonical, so an agent built against the draft finds Connect instead of concluding it does not speak A2A.

And an unsupported revision is refused loudly rather than guessed at: a version string the MCP endpoint does not know produces an error whose `data.supported` array lists the four it does. A client can recover automatically from that.

## The descriptions are generated, not maintained

`openapi.yaml`, the Arazzo workflows, the AsyncAPI document and the agent card are all produced by the documentation build from the same capability registry the pages come from. Nobody edits them by hand, and the next build overwrites them. That is what stops a description drifting from behaviour — the classic failure where an API stays the same and its document quietly stops matching.

It also means the description's version follows the build. If you have cached a copy, compare `info.version` and the `generated` date rather than diffing YAML, and regenerate your client when either moves.

## What not to pin to

- **A search result's position.** Ranking depends on the index, which rebuilds when the corpus does. Pin to a `url` or an `id`.
- **The wording of a `message`.** Error messages are prose for a log; branch on `error.code` instead, with a default branch for a code you do not know.
- **The shape of an id.** Ids are stable and opaque. The letters are not a classification — `kind` is a field you can read.
- **The absence of a field.** Treat unknown fields as ignorable rather than as an error, so an additive change to a response does not break your parser.
- **A page's prose.** It is regenerated. Cache on the page's `updated` date, which exists for exactly this.

> **Note** The protocol version strings quoted across these pages — MCP `2026-07-28`, A2A `1.0.0`, OpenAPI `3.1.0`, Arazzo `1.1.0`, AsyncAPI `3.1.0`, JSON-RPC `2.0` — are the ones the generated artefacts actually carry. The description is written to OpenAPI 3.1.0 rather than a later revision because tool support for 3.1 is universal and nothing in this API needs a newer feature.

## Questions

### Is there a `/v1/` prefix or an `Accept-Version` header?

Neither. Paths are unversioned and no version header is read on the HTTP API. The MCP endpoint is the only surface that negotiates, and it does so per request rather than per client.

### How will I notice a change?

By the version and `generated` fields on the descriptions and the status endpoint, and by the `updated` date on individual pages. [The changelog and its feed](/developers/changelog-feed/) is the published record of user-visible change.

### Should I pin my client to one MCP revision?

Send the revision you implement and read the one you are given back. Pinning in the sense of refusing anything else is what makes a client brittle; the endpoint already tells you which revisions it accepts.

## Related

- [The OpenAPI description](https://connectbyjbrh.com/developers/openapi-description/)
- [The Connect MCP server](https://connectbyjbrh.com/developers/mcp-server/)
- [The Connect Agent Card](https://connectbyjbrh.com/developers/agent-card/)
- [The changelog and its feed](https://connectbyjbrh.com/developers/changelog-feed/)
- [Stable identifiers](https://connectbyjbrh.com/developers/entity-ids/)
- [OpenAPI](https://connectbyjbrh.com/docs/protocols/openapi/)

## What this page is based on

- `backend/app/mcp_server.py` — SUPPORTED_VERSIONS and version negotiation
- `backend/app/a2a_server.py` — the legacy well-known path
- `webapp/developers/openapi.yaml`
- `webapp/.well-known/agent-card.json`
- Protocol versions in docs-source/facts.py
