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.
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.
{
"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:
{
"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.
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.
| Change | New version? |
|---|---|
| New endpoint | No |
| New optional request field | No |
| New response field | No |
| New value in an open response enum | No |
| Removing or renaming a field | Yes |
| Changing a field's type or meaning | Yes |
| Tightening request validation | Yes |
| Changing a default value | Yes |
| Changing an error code or status | Yes |
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.