Threa Developers

Versioning

API versions.

Threa versions the API by date. Each key is pinned to a version when you create it, so your integration keeps seeing the same shapes until you decide to move. When a version introduces a breaking change, your key stays on the older one and you opt in when you are ready.

How it works

A version is a date. The current version is 2026-07-12. When you create an API key it is pinned to whatever version is current at that moment, and that pin decides which shapes the key sees on every request. You do not have to send anything for this to work.

When we later ship a version with a breaking change, existing keys are unaffected. You test the new version by sending the Threa-Version header on individual requests, and when your integration is ready you either send that header everywhere or create a new key pinned to the newer version.

The header

Send Threa-Version with a version date to override your key's pin for a single request. A valid header takes precedence over the pin.

override the pin for one request
curl /api/v1/workspaces//streams \
  -H "Authorization: Bearer " \
  -H "Threa-Version: 2026-07-12"

The value must be one of the known versions exactly. An unknown or malformed value returns 400 with the code INVALID_API_VERSION and the list of known versions in the error, so a typo fails loudly instead of being treated as the latest. Every response that resolved a version echoes it in a Threa-Version response header, so you can always confirm which shapes you received. The 400 for an unknown version has no resolved version, so it carries no echo header.

400 on an unknown version
{
  "error": "Unknown API version \"2020-01-01\". Known versions: 2026-07-12",
  "code": "INVALID_API_VERSION"
}

Reading and changing the pin

GET /api/v1/workspaces/{workspaceId}/me reports the key's version state alongside its identity, so an agent can discover its pin with a call it already makes:

apiVersion block in the /me response
{
  "data": {
    "kind": "user",
    "workspaceId": "ws_...",
    "userId": "usr_...",
    "apiVersion": {
      "pinned": "2026-07-12",
      "resolved": "2026-07-12",
      "current": "2026-07-12",
      "supported": ["2026-07-12"]
    }
  }
}

pinned is the key's pin, or null when the key is unpinned. resolved is the version this request used.

detect that a newer version exists
interface ApiVersionInfo {
  pinned: string | null
  resolved: string
  current: string
  supported: string[]
}

const res = await fetch("/api/v1/workspaces//me", {
  headers: { Authorization: "Bearer " },
})
const { data } = (await res.json()) as { data: { apiVersion: ApiVersionInfo } }

if (data.apiVersion.resolved !== data.apiVersion.current) {
  // A newer version exists. Test against it by sending
  // "Threa-Version": data.apiVersion.current on individual requests.
}

Pins belong to the key's owner and are managed in the app, not through the public API: PATCH the key in the workspace's key management with {"apiVersion": "<date>"} to re-pin it, or {"apiVersion": null} to unpin it. An unpinned key always uses the current version, including future breaking changes, so unpin only if you update your integration as versions ship. To adopt newer shapes without touching the pin, send the header or create a new key.

Breaking vs additive changes

Additive changes do not get a new version and can arrive on your current one at any time. Plan for them. These are new endpoints, new optional request fields, new response fields, and new values in response enums that are documented as open sets. Write your client so an unexpected field or enum value is ignored rather than rejected.

Breaking changes get a new dated version and never reach a key pinned to an earlier one. A change is breaking when it removes or renames a field, changes a field's type or meaning, tightens request validation, changes a default, or changes an error code or status. This is the same split Stripe uses.

ChangeNew version?
New endpointNo
New optional request fieldNo
New response fieldNo
New value in an open response enumNo
Removing or renaming a fieldYes
Changing a field's type or meaningYes
Tightening request validationYes
Changing a default valueYes
Changing an error code or statusYes

Support window

A version stays supported for at least twelve months after it is succeeded, so a key pinned to an older version keeps working for that long once a newer version ships.

The path prefix

The URL prefix does not change with versions. Every endpoint stays under /api/v1, and the Threa-Version header carries the version. The v1 segment is a stable prefix, so paths you hardcode today keep working. Version negotiation happens entirely through the header and the key's pin.

Changelog

Each dated version is listed here with what changed. The current version is 2026-07-12.

Each version has its own OpenAPI spec at /openapi/<version>.json, and /openapi.json is the current one. The reference renders each version at /developers/reference/<version>, with a version picker at the top.

2026-07-12

Initial versioned API.