> ## Documentation Index
> Fetch the complete documentation index at: https://developers.meshapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API versioning

> Mesh versions its contract by date. Pin a version with X-Mesh-Version so a future change to a response shape cannot change it underneath your code.

Mesh versions its API **by date**, not by a number in the URL. The current version is
`2026-08`, and you select one by sending a header:

```bash theme={null}
curl https://api.meshapi.ai/v1/models \
  -H "Authorization: Bearer rsk_..." \
  -H "X-Mesh-Version: 2026-08"
```

The `/v1` in the path is **not** a version. It is a stable namespace and it is not going
to change — `/v2` is not planned. Everything about the shape of a request or response is
governed by the dated version instead.

## Why a version header

Additive changes — a new field, a new optional parameter, a new endpoint — are shipped
continuously and do not need a version. You get them without doing anything.

A dated version exists for the changes that are *not* safe: renaming a field, removing
one, or changing what a value means. Rather than break your integration or freeze the API
forever, those changes land in a new dated version, and requests pinned to an older one
keep receiving the older shape.

Pinning is what makes that a guarantee rather than a coincidence. A pinned request states
which shape your code can parse, so a change to the current shape cannot reach it.

## If you send no header

You are served the **baseline** — the *oldest* supported version, which today is `2026-08`.

That is a deliberate choice, and worth understanding because it inverts what people
usually expect from a default:

<Note>
  Publishing a new version never moves existing traffic. Because the baseline is the
  oldest supported version rather than the newest, an integration that sends no header
  keeps getting the same shapes after a new version ships. You move only by pinning the
  new version yourself.
</Note>

So there is no urgency to pin — but until you do, your code has not stated what it can
parse, and the guarantee above does not apply to it.

## Which versions are served

`GET /v1/api-versions` lists them, oldest first. It takes either a dashboard session token
or an `rsk_` API key.

```bash theme={null}
curl https://api.meshapi.ai/v1/api-versions \
  -H "Authorization: Bearer rsk_..."
```

```json theme={null}
[
  {
    "label": "2026-08",
    "status": "ga",
    "released": "2026-08-15",
    "deprecated_on": null,
    "sunset_on": null,
    "notes_url": null,
    "baseline": true,
    "latest": true
  }
]
```

`baseline` marks what an unpinned request gets; `latest` marks the newest. They are the same
entry today and will diverge the moment a second version ships — `latest` is deliberately
**not** the default.

<Tip>
  Worth wiring into your own tests: assert the version your code pins still appears here, and
  that its `status` is not `sunset`. That turns "our pin went stale" into a failing test
  instead of a 400 in production.
</Tip>

## Pinning a whole API key

Rather than sending the header on every request, you can pin the key itself — useful when
the calling code is not yours to change, or when a whole integration should sit on one
version.

Set it when creating a key, or on an existing one:

```bash theme={null}
curl -X PATCH https://api.meshapi.ai/v1/keys/{key_id} \
  -H "Authorization: Bearer <session-token>" \
  -H "Content-Type: application/json" \
  -d '{"api_version": "2026-08"}'
```

Send `"api_version": null` to clear it. In the dashboard the same setting is on the key's
**API Version** field under **API Keys**.

Resolution order, highest first:

1. The `X-Mesh-Version` **header** on the request — always wins.
2. The **key's** stored pin.
3. The **baseline**.

<Note>
  A key's pin is resolved when the key is created, from your organisation's default at that
  moment — so a key created without an explicit version may show a concrete one afterwards
  rather than staying empty. Changing your org default later does not move existing keys.
</Note>

An unsupported version stored on a key is treated differently from an unsupported header: the
request falls back to the baseline rather than failing. A typo in *your* header should be
loud; a version we retired out from under a stored pin should not break your traffic.

## Unsupported versions are rejected, not downgraded

If you send a version Mesh does not serve, the request fails:

```json theme={null}
{
  "error": {
    "code": "invalid_api_version",
    "message": "API version '1999-01' is not supported. Supported versions: 2026-08.",
    "supported_versions": ["2026-08"]
  },
  "request_id": "req_01ARZ3NDEKTSV4RRFFQ69G5FAV"
}
```

`400`, with the versions that *are* served, and no fallback.

<Warning>
  This is intentional, and it is the reason pinning is trustworthy. If a typo or a stale
  constant quietly fell back to some other version, you would believe you were pinned when
  you were not, and find out from a shape mismatch in production instead. A `400` on the
  first call is the better failure.

  For the same reason, an **empty** value (`X-Mesh-Version:` with nothing after it) is
  treated as a malformed pin, not as "no pin". If your client builds the header
  conditionally, omit it entirely when you have no version to send rather than sending it
  blank.
</Warning>

Validation runs before authentication, so a bad version is rejected with `400` even if
the key is also wrong.

## Reading the version that served a request

Every response carries `X-Mesh-Version` with the version that served it — including error
responses, and including requests that sent no header at all. So you can confirm what you
actually got rather than assume it, and you can see which version a stored key pin resolved to
without looking the key up.

```bash theme={null}
curl -i https://api.meshapi.ai/v1/models -H "Authorization: Bearer rsk_..."
# ...
# x-mesh-version: 2026-08
```

It is in the CORS expose-headers allowlist, so browser JavaScript can read it too.

## Format

A version label is `YYYY-MM` — four digits, a hyphen, two digits, e.g. `2026-08`. Anything
else is rejected with the same `400 invalid_api_version`.

Pass it as a literal string. Do not compute it from the current date: the label names a
contract, not today's month, and there is no version for most months.

## Which version to pin

Pin the version you developed and tested against, and change it deliberately.

Concretely: hold the label in one constant in your codebase, send it on every request, and
treat bumping it as a change with its own review — because a new version exists precisely
when some shape is different, so moving to it means reading what changed.

<Warning>
  Do not read the version from configuration that varies by environment. A staging
  deployment pinned to one version and production pinned to another means the shape your
  tests exercise is not the shape production parses, which defeats the point.
</Warning>

## What is not covered by a version

* **Model availability and pricing.** Models are added, retired, and repriced
  continuously. Pin a version and you still get the current catalogue — see
  [Models](/docs/reference/models).
* **Rate limits and quotas.** Governed by your plan, not the contract. See
  [Rate limits](/docs/getting-started/rate-limits).
* **Provider behaviour.** A model's output quality or latency is the provider's, and no
  version freezes it.
* **Bug fixes.** A response that did not match its documented shape gets corrected in
  place, in every version.

## Realtime

The realtime WebSocket API (`/v1/realtime`) negotiates separately and does not read this
header. Sending it on the handshake has no effect.

## Knowing something is going away, without reading this page

When a version or an endpoint is scheduled for retirement, its responses start carrying
standard headers — so your monitoring can find out before a human does:

| Header        | What it says                                                                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Deprecation` | When it was deprecated. An [RFC 9745](https://www.rfc-editor.org/rfc/rfc9745.html) structured-field date — `@1788220800`, seconds since the epoch |
| `Sunset`      | When it stops being served. An [RFC 8594](https://www.rfc-editor.org/rfc/rfc8594.html) HTTP-date — `Wed, 01 Sep 2027 00:00:00 GMT`                |
| `Link`        | `rel="deprecation"` pointing at the migration notes, when there are any                                                                           |

<Warning>
  **The two dates are in different formats** — a unix timestamp and an HTTP-date. If you parse
  one and reuse the parser on the other you will silently get nothing. That is a property of the
  RFCs, not of Mesh.
</Warning>

`Sunset` may be absent while `Deprecation` is present: "deprecated, retirement date to be
announced" is the normal state at announcement time. Absence means undecided, not imminent.

These arrive on **every** response for an affected route, including errors — a `429` from a
deprecated endpoint is still deprecated, and an error response is often the only one a client
logs.

Nothing is deprecated today, so nothing currently emits them.

If you read these from browser JavaScript, note they are already in the CORS
`Access-Control-Expose-Headers` allowlist along with `X-Mesh-Version`.

## Support window

Every version `GET /v1/api-versions` lists is served. `2026-08` is currently the only one, so
nothing is scheduled for retirement and no pin can go stale today.

A version is never withdrawn without notice. The retirement policy — how long a version is
served after a newer one ships, and how much notice a sunset gets — is published before there
is a second version to move between. If you need those numbers to commit to a pin now,
[contact support](/docs/reference/support).
