Threa Developers

    API versions.

    Versions are dates. Each key is pinned to the version current when you made it, and breaking changes reach you only when you opt in.

    How it works

    A version is a date. The current version is 2026-08-21. 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-08-21"

    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-08-21",
      "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-08-21",
          "resolved": "2026-08-21",
          "current": "2026-08-21",
          "supported": ["2026-08-21"]
        }
      }
    }

    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-08-21.

    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-08-21

    Shared-message and quote references pin a source revision and optional span. `slots` keys carry the reference (`shared:<messageId>[@<version>[:<from>-<to>]]`), `ok` slots gain `version`, `currentRevision` and `range`, and `content` is the revision the reference names rather than the source as it now reads. Pins before this version still get one `shared:<messageId>` key per source, the whole-message slot at the highest version, without the pin fields; a reference to a span of a message is omitted for those pins, since that shape cannot say it is a fragment. Affected operations: completeBotInvocation, findMessagesByMetadata, listConversationMessages, listMessages, searchMessages, sendMessage, updateMessage

    2026-07-24

    Message responses now include a top-level `slots` map hydrating cross-stream shared-message pointers (keyed `shared:<messageId>`, markdown content only). Pins before this version have the map stripped. Affected operations: completeBotInvocation, findMessagesByMetadata, listConversationMessages, listMessages, searchMessages, sendMessage, updateMessage

    2026-07-22

    Threads can now anchor on cards: stream `parentMessageId` became `anchorId`, and current-version delegation completions put results directly in the card thread while 2026-07-12 retains its synthetic message anchor. Affected operations: completeDelegation, getStream, listStreams, updateStream

    2026-07-12

    Initial versioned API.