# API Versioning

The Roam API is **versioned by date** so we can evolve response and webhook payload shapes over time
without breaking existing integrations. Your integration is pinned to a single version and never
changes shape underneath you — you adopt new versions deliberately, when you're ready.

This page explains how versions work and what you need to do. If you're moving from the v0 Alpha
API, start with the [Migration Guide: v0 to v1](/docs/guides/migration-v0-to-v1) first — that's a
one-time, larger jump. This page is about the ongoing, incremental versions _within_ v1.

## How versions are named

Versions are dates, in `YYYY-MM-DD` form. The current version is:

```
2026-08-20
```

`/v1` in the base URL (`https://api.ro.am/v1`) is the **API generation** — a stable namespace that
won't churn. The dated version is the **revision within it** that controls the exact shape of
responses and webhook payloads. We will not mint a `/v2` URL for routine shape changes; those ship
as new dated versions.

> v0 is the previous generation (`https://api.ro.am/v0`). It is frozen and not part of the
> dated-version scheme. See the [Migration Guide](/docs/guides/migration-v0-to-v1).

## Your integration's version

Every integration (API key, OAuth app, or Personal Access Token) has a **default version**, set
**once when the integration is created** and pinned there:

- **New integrations** are created on the **latest** version available at that time.
- **Existing integrations** stay on the version they were created with.
- **Your version never advances automatically.** A new Roam API version shipping does not change
  your integration's behavior. You upgrade when you choose to.

This is the key guarantee: code written against the version you're on keeps working, byte-for-byte,
until you decide to move.

You can confirm which version a response was rendered with — every API response includes a header
matching the version that shaped the body. For a client on the current default:

```
Roam-Version: 2026-08-20
```

(Older integrations still pinned to earlier versions receive those pins — for example
`Roam-Version: 2026-07-23` or the baseline `2026-06-01` — and the corresponding shapes.)

## Overriding the version per request

To try a different version for a single request without changing your default, send the
`Roam-Version` request header. Example — force the **baseline** shape while your client default is
newer:

```bash
curl https://api.ro.am/v1/chat.history?chatId=88bebce7-6cbb-4666-96f9-5c02d73e6661 \
  -H "Authorization: Bearer $ROAM_TOKEN" \
  -H "Roam-Version: 2026-06-01"
```

Or target the current version explicitly:

```bash
curl https://api.ro.am/v1/chat.history?chatId=88bebce7-6cbb-4666-96f9-5c02d73e6661 \
  -H "Authorization: Bearer $ROAM_TOKEN" \
  -H "Roam-Version: 2026-08-20"
```

Resolution order for a REST request:

1. The `Roam-Version` request header, if present.
2. Otherwise, your integration's pinned default version.
3. If neither is set (pre-versioning clients), the baseline version `2026-06-01`.

Sending an unsupported version returns `400` with the list of supported versions.

Most integrations never send the header — they rely on their pinned default. Use the header to
**test** a different version before adopting it.

## Webhook versions

Webhooks are versioned too, but because Roam initiates the delivery there is no request to carry a
header. Instead, **the version is fixed on the subscription**:

- When you create a subscription with [`webhook.subscribe`](/docs/webhooks/webhook-subscribe), it
  captures your integration's default version (or an explicit `apiVersion` you pass in the request
  body), and that subscription delivers that shape from then on.
- Each delivery includes the `Roam-Version` header — and, from version `2026-07-07` onward, the
  envelope's `apiVersion` field — so your handler can confirm the shape it's receiving.

To move a subscription to a newer payload shape, create a new subscription on the new version
(optionally alongside the old one) and retire the old one once you've cut over.

## What changes between versions — and what doesn't

We make **backward-compatible (additive) changes at any time, on every version, without a new
version number.** Write your integration to tolerate them. Specifically, you should expect and
safely ignore:

- **New fields** in responses and webhook payloads.
- **New endpoints** and **new webhook event types**.
- **New optional request parameters.**
- **New values** in existing enum-style fields.

> **Build defensively:** ignore unknown fields, and don't assume an enum will only ever return the
> values you've seen. An integration that rejects unexpected fields or values will break on a
> _non-breaking_ change.

A **new dated version** is only introduced for **backward-incompatible** changes, such as:

- Removing or renaming a field.
- Changing a field's type or structure (for example, wrapping a payload in an envelope).
- Changing a default behavior.

Because these only land in a new version, and your integration is pinned, they never reach you until
you opt in.

## Version history

| Version | Highlights |
| ------- | ---------- |
| **`2026-08-20`** (current default) | Chat membership for v1 push and most REST: organization apps hear a group only if they are a member; personal tokens ride the **owner's** membership (every message in those chats, not mention-only). `{mention:true}` restores mention-only. Organization [`chat.post`](/docs/api/chat-post) and [`chat.startStream`](/docs/api/chat-start-stream) to a public group without joining remain allowed. Organization [`chat.search`](/docs/api/chat-search) includes unjoined public groups in the bot's roam. New API keys, OAuth apps, and PATs pin here. |
| **`2026-08-07`** | [Slack mention grammar](/docs/api/chat-post) in message `text` on reads: groups render as `<!subteam^uuid>` (previously `<@uuid>`) and the broadcast as `<!channel>` (previously `<@all>`). Principals stay `<@uuid>`. The `mentions` list is unchanged. |
| **`2026-07-23`** | Same delivery shapes as `2026-07-07`. |
| **`2026-07-07`** | Common [webhook event envelope](/docs/webhooks/webhooks#event-envelope): `type`, `eventId`, `timestamp`, `apiVersion`, `data`. Route on `type`; business fields live under `data`. |
| **`2026-06-01`** (baseline) | First dated v1 pin. Webhook payloads are **bare** (no envelope). Pre-versioning clients that send no pin resolve here. |

Subscriptions pinned to `2026-06-01` keep bare payload shapes byte-for-byte. Subscriptions on
`2026-07-07` or newer receive the envelope. Moving a `2026-07-07` client to `2026-07-23` requires
no code changes. Moving to `2026-08-07` changes only how mention tokens render in message `text`
on reads (`chat.history`, `chat.search`, `chat.link.resolve`, and the `chat.message`
webhook). Moving to `2026-08-20` applies membership to v1 chat push and most REST
(organization `chat.post` / `chat.startStream` to public groups stay allowed
without joining; organization `chat.search` includes unjoined public groups).
**Writes are not versioned**:
every version accepts both mention grammars (plus Slack-style `|label` suffixes and the
legacy `<@all>` broadcast alias) on `chat.post` and `chat.update`.

**Additive changes** (all v1 pins, no new date): principal hydration / type fields, optional
`sender` display overrides on message reads, and similar. Strict JSON decoders must ignore unknown
properties. See [Identity & Principals](/docs/guides/identity-and-principals) and
[Sender Profiles](/docs/guides/sender-profiles).

The v0 → v1 generation jump (IDs, endpoint renames, event names) is separate from dated pins —
use the [Migration Guide](/docs/guides/migration-v0-to-v1).

## Adopting a new version

1. Review the [version history](#version-history) (and migration notes for the target pin).
2. **Test** against it using the `Roam-Version` header (REST) or a second
   [`webhook.subscribe`](/docs/webhooks/webhook-subscribe) on the new version (webhooks), without
   touching production.
3. Update your code to the new shapes.
4. Update your integration's pinned default to the new version, and migrate any webhook
   subscriptions.

There's no rush — see the support window below.

## Deprecation policy

- Each version is supported for **at least 12 months** after a newer version is released.
- As a version approaches end of life, REST responses include `Deprecation` and `Sunset` headers
  with the shutdown date.
- After a version is sunset, REST requests pinned to it return `410 Gone`. We will contact you well
  before this happens; no version is retired while integrations are still using it.

## Best practices

- **Pin explicitly in code you care about.** Relying on your account default is fine, but sending
  `Roam-Version` makes the shape your integration expects unambiguous and self-documenting.
- **Tolerate additive changes.** Ignore unknown fields and unrecognized enum values rather than
  failing.
- **Check the delivery header on webhooks.** Use `Roam-Version` to route payloads if you ever run
  more than one version.
- **Treat cursors and webhook timestamps as opaque.** Pagination cursors are server-issued tokens —
  pass `nextCursor` back as `cursor` unchanged. Invalid cursors return `error: "invalid_cursor"`.
  See [Responses and Errors](/docs/guides/responses-and-errors).
- **Version pin is stamped at client creation.** New API keys, OAuth apps, and PATs receive the
  latest published version at registration time (currently `2026-08-20`). The pin does not change
  when you redeploy or when Roam ships a newer version.

## Questions?

Contact us via [Roam Support Chat](https://ro.am/support/contact-us) or email
[developer@ro.am](mailto:developer@ro.am).