Reference
API reference.
Every endpoint, generated from the OpenAPI spec we ship. Set your workspace ID and key in the Credentials panel (top right) and the read-only examples run against your own workspace as you read.
The base is https://app.threa.io, under the stable
/api/v1 prefix and scoped to a workspace:
/api/v1/workspaces/{workspaceId}/…. The v1
segment does not move as the API evolves; versions are dated and selected with
the Threa-Version header, described in
Versioning. Every request
authenticates with Authorization: Bearer <key>. Error
shapes, paging, idempotency, and rate limits live in
Operations.
Identity
Confirm who a key belongs to and list the bots you own.
/api/v1/workspaces/{workspaceId}/me Get the authenticated principal
Returns a discriminated union describing the authenticated principal: the API-key owner (`kind: "user"`) or the bot whose key is in use (`kind: "bot"`). Also reports the key's API version pin, the version this request resolved to, and the supported versions. Used by clients (e.g. the OpenClaw channel plugin) to verify their key and discover their identity after pairing.
Scope none — any valid key
Response 200
dataobjectrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/me/bots List my personal bots
For user-scoped keys: lists the authenticated user's personal bots, optionally filtered by trait. Used by the frontend to enumerate quick-switcher commands. Bot-scoped keys receive 403.
Scope none — any valid key
Query parameters
traitsstringResponse 200
dataobject[]requiredidstringrequiredworkspaceIdstringrequiredtraitsstring[]requiredslugstring | nullrequirednamestringrequireddescriptionstring | nullrequiredavatarEmojistring | nullrequiredavatarUrlstring | nullrequiredarchivedAtstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredtypestringrequiredownerUserIdstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
Streams
List and inspect streams (channels, scratchpads, threads)
Recipes Notify a stream from CI →
/api/v1/workspaces/{workspaceId}/streams List streams
List streams accessible to this API key, with optional type and text filters.
Scope streams:read
Query parameters
typeanyrequiredquerystringafterstringlimitintegerincludeArchivedstringResponse 200
dataobject[]requiredidstringrequiredtypestringrequireddisplayNamestringrequiredslugstringdescriptionstringvisibilitystringrequiredmemoryModestringrequiredGAM memory automation gate: 'auto' extracts memos, 'off' disables it
parentStreamIdstringrootStreamIdstringanchorIdstringCanonical id of the timeline item a thread anchors on. The prefix is the kind: 'msg_…' for a message, 'event_…' for a card. Present on threads only.
createdAtstringrequiredarchivedAtstringhasMorebooleanrequiredcursorstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/streams/{streamId} Get a stream
Scope streams:read
Path parameters
streamIdstringrequiredStream ID (prefixed ULID)
Response 200
dataobjectrequiredidstringrequiredtypestringrequireddisplayNamestringrequiredslugstringdescriptionstringvisibilitystringrequiredmemoryModestringrequiredGAM memory automation gate: 'auto' extracts memos, 'off' disables it
parentStreamIdstringrootStreamIdstringanchorIdstringCanonical id of the timeline item a thread anchors on. The prefix is the kind: 'msg_…' for a message, 'event_…' for a card. Present on threads only.
createdAtstringrequiredarchivedAtstringStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/streams/{streamId} Set a stream's description
Set the stream's description from markdown (parsed to rich text, same as message content). Send an empty string to clear it. User-scoped keys attribute the change to the key owner; workspace-scoped keys to the bot.
Scope streams:write
Path parameters
streamIdstringrequiredStream ID (prefixed ULID)
Request body
descriptionstringrequiredResponse 200
dataobjectrequiredidstringrequiredtypestringrequireddisplayNamestringrequiredslugstringdescriptionstringvisibilitystringrequiredmemoryModestringrequiredGAM memory automation gate: 'auto' extracts memos, 'off' disables it
parentStreamIdstringrootStreamIdstringanchorIdstringCanonical id of the timeline item a thread anchors on. The prefix is the kind: 'msg_…' for a message, 'event_…' for a card. Present on threads only.
createdAtstringrequiredarchivedAtstringStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/streams/{streamId}/members List stream members
Scope streams:read
Path parameters
streamIdstringrequiredStream ID (prefixed ULID)
Query parameters
afterstringlimitintegerResponse 200
dataobject[]requireduserIdstringrequirednamestringrequiredslugstringrequiredavatarUrlstringjoinedAtstringrequiredhasMorebooleanrequiredcursorstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
Messages
Read, send, update, and delete messages
Recipes Notify a stream from CI →Mirror a search into your own tool →
/api/v1/workspaces/{workspaceId}/messages/search Search messages
Full-text and optional semantic search across accessible streams.
Scope messages:search
Request body
querystringrequiredsemanticbooleanrequiredexactbooleanrequiredstreamsstring[]fromstringtypestring[]beforestringafterstringlimitintegerrequiredResponse 200
dataobject[]requiredidstringrequiredstreamIdstringrequiredsequencestringrequiredNumeric sequence as string
contentstringrequiredauthorIdstringrequiredauthorTypestringrequiredauthorDisplayNamestringreplyCountintegerrequiredmetadataobjectrequiredExternal references attached by the sender. Always present; empty when unset.
editedAtstringcreatedAtstringrequiredranknumberrequiredslotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/streams/{streamId}/messages List messages in a stream
Cursor-paginated message list. Use `before` or `after` sequence numbers.
Scope messages:read
Path parameters
streamIdstringrequiredStream ID (prefixed ULID)
Query parameters
beforestringafterstringlimitintegerResponse 200
dataobject[]requiredidstringrequiredstreamIdstringrequiredsequencestringrequiredNumeric sequence as string
authorIdstringrequiredauthorTypestringrequiredauthorDisplayNamestringcontentstringrequiredreplyCountintegerrequiredthreadStreamIdstringclientMessageIdstringsentViastringPresent when message was sent via API on behalf of a user
metadataobjectrequiredExternal references attached by the sender. Always present; empty when unset.
attachmentsobject[]idstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringwidthintegerheightintegereditedAtstringcreatedAtstringrequiredhasMorebooleanrequiredslotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/streams/{streamId}/messages Send a message
Send a message. Workspace-scoped keys send as a bot; user-scoped keys send on behalf of the key owner. Optionally declare the message's conversation via `conversation`: `{intent: "new"}` starts a fresh conversation, `{intent: "existing", conversationId}` posts into one under the same root stream.
Scope messages:write
Path parameters
streamIdstringrequiredStream ID (prefixed ULID)
Request body
contentstringrequiredclientMessageIdstringmetadataobjectconversationobjectResponse 201
dataobjectrequiredidstringrequiredstreamIdstringrequiredsequencestringrequiredNumeric sequence as string
authorIdstringrequiredauthorTypestringrequiredauthorDisplayNamestringcontentstringrequiredreplyCountintegerrequiredthreadStreamIdstringclientMessageIdstringsentViastringPresent when message was sent via API on behalf of a user
metadataobjectrequiredExternal references attached by the sender. Always present; empty when unset.
attachmentsobject[]idstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringwidthintegerheightintegereditedAtstringcreatedAtstringrequiredconversationIdstringThe conversation the message was assigned to; present when the request declared a `conversation`.
slotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
201Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/messages/find-by-metadata Find messages by metadata
Find non-deleted messages whose `metadata` contains all the given key/value pairs (AND-containment). Useful for dedup flows, e.g. 'has a message already been posted for this GitHub PR event?'.
Scope messages:read
Request body
metadataobjectrequiredstreamIdstringlimitintegerrequiredResponse 200
dataobject[]requiredidstringrequiredstreamIdstringrequiredsequencestringrequiredNumeric sequence as string
authorIdstringrequiredauthorTypestringrequiredauthorDisplayNamestringcontentstringrequiredreplyCountintegerrequiredthreadStreamIdstringclientMessageIdstringsentViastringPresent when message was sent via API on behalf of a user
metadataobjectrequiredExternal references attached by the sender. Always present; empty when unset.
attachmentsobject[]idstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringwidthintegerheightintegereditedAtstringcreatedAtstringrequiredslotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/messages/{messageId} Update a message
Update a message you previously sent via API.
Scope messages:write
Path parameters
messageIdstringrequiredMessage ID (prefixed ULID)
Request body
contentstringrequiredResponse 200
dataobjectrequiredidstringrequiredstreamIdstringrequiredsequencestringrequiredNumeric sequence as string
authorIdstringrequiredauthorTypestringrequiredauthorDisplayNamestringcontentstringrequiredreplyCountintegerrequiredthreadStreamIdstringclientMessageIdstringsentViastringPresent when message was sent via API on behalf of a user
metadataobjectrequiredExternal references attached by the sender. Always present; empty when unset.
attachmentsobject[]idstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringwidthintegerheightintegereditedAtstringcreatedAtstringrequiredslotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/messages/{messageId} Delete a message
Delete a message you previously sent via API.
Scope messages:write
Path parameters
messageIdstringrequiredMessage ID (prefixed ULID)
Status codes
204No content400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Conversations
Browse first-class conversations — topic-level groupings of messages under a root stream and its threads
/api/v1/workspaces/{workspaceId}/conversations List conversations
Cursor-paginated conversation feed across accessible streams, newest activity first. Filter with `streamId` (scopes to that stream's root and its threads) and `status`.
Scope messages:read
Query parameters
streamIdstringstatusstringafterstringlimitintegerResponse 200
dataobject[]requiredidstringrequiredstreamIdstringrequiredAnchor stream the conversation lives in (may be a thread)
rootStreamIdstringrequiredEffective root of the anchor — the stream whose access governs the conversation
topicSummarystring | nullrequiredsummarystring | nullrequiredstatusstringrequiredmessageCountintegerrequiredNumber of primary member messages
participantIdsstring[]requiredDistinct author ids of the member messages
lastActivityAtstringrequiredcreatedAtstringrequiredupdatedAtstringrequiredhasMorebooleanrequiredcursorstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/conversations/{conversationId} Get a conversation
Scope messages:read
Path parameters
conversationIdstringrequiredConversation ID (prefixed ULID)
Response 200
dataobjectrequiredidstringrequiredstreamIdstringrequiredAnchor stream the conversation lives in (may be a thread)
rootStreamIdstringrequiredEffective root of the anchor — the stream whose access governs the conversation
topicSummarystring | nullrequiredsummarystring | nullrequiredstatusstringrequiredmessageCountintegerrequiredNumber of primary member messages
participantIdsstring[]requiredDistinct author ids of the member messages
lastActivityAtstringrequiredcreatedAtstringrequiredupdatedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/conversations/{conversationId}/messages List a conversation's messages
The conversation's member messages in chronological order, cursor-paginated. Messages can span the conversation's root stream and its threads; each message carries its own `streamId`.
Scope messages:read
Path parameters
conversationIdstringrequiredConversation ID (prefixed ULID)
Query parameters
afterstringlimitintegerResponse 200
dataobject[]requiredidstringrequiredstreamIdstringrequiredsequencestringrequiredNumeric sequence as string
authorIdstringrequiredauthorTypestringrequiredauthorDisplayNamestringcontentstringrequiredreplyCountintegerrequiredthreadStreamIdstringclientMessageIdstringsentViastringPresent when message was sent via API on behalf of a user
metadataobjectrequiredExternal references attached by the sender. Always present; empty when unset.
attachmentsobject[]idstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringwidthintegerheightintegereditedAtstringcreatedAtstringrequiredhasMorebooleanrequiredcursorstring | nullrequiredslotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Memos
Search preserved workspace knowledge and inspect memo provenance
Recipes Find out what was decided, and why →
/api/v1/workspaces/{workspaceId}/memos/search Search memos
Search preserved workspace memos with semantic, exact, or recent-first retrieval.
Scope memos:read
Request body
querystringrequiredexactbooleanstreamsstring[]memoTypestring[]knowledgeTypestring[]tagsstring[]scopestringbeforestringafterstringlimitintegerrequiredResponse 200
dataobject[]requiredmemoobjectrequiredidstringrequiredworkspaceIdstringrequiredmemoTypestringrequiredsourceMessageIdstring | nullrequiredsourceConversationIdstring | nullrequiredtitlestringrequiredabstractstringrequiredkeyPointsstring[]requiredsourceMessageIdsstring[]requiredparticipantIdsstring[]requiredknowledgeTypestringrequiredtagsstring[]requiredparentMemoIdstring | nullrequiredstatusstringrequiredversionintegerrequiredrevisionReasonstring | nullrequiredauthoredByKindstringrequiredsourceSessionIdstring | nullrequiredscopestringrequiredscopeUserIdstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredarchivedAtstring | nullrequireddistancenumberrequiredsourceStreamobject | nullrequiredrootStreamobject | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/memos/{memoId} Get a memo
Retrieve a memo together with source stream and source message provenance.
Scope memos:read
Path parameters
memoIdstringrequiredMemo ID (prefixed ULID)
Response 200
dataobjectrequiredmemoobjectrequiredidstringrequiredworkspaceIdstringrequiredmemoTypestringrequiredsourceMessageIdstring | nullrequiredsourceConversationIdstring | nullrequiredtitlestringrequiredabstractstringrequiredkeyPointsstring[]requiredsourceMessageIdsstring[]requiredparticipantIdsstring[]requiredknowledgeTypestringrequiredtagsstring[]requiredparentMemoIdstring | nullrequiredstatusstringrequiredversionintegerrequiredrevisionReasonstring | nullrequiredauthoredByKindstringrequiredsourceSessionIdstring | nullrequiredscopestringrequiredscopeUserIdstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredarchivedAtstring | nullrequireddistancenumberrequiredsourceStreamobject | nullrequiredrootStreamobject | nullrequiredsourceMessagesobject[]requiredidstringrequiredstreamIdstringrequiredstreamNamestringrequiredauthorIdstringrequiredauthorTypestringrequiredauthorNamestringrequiredcontentstringrequiredcreatedAtstringrequiredsuccessorMemoIdstring | nullrequiredcapturedByPersonaNamestring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Attachments
Search attachments, inspect extracted content, and fetch download URLs
/api/v1/workspaces/{workspaceId}/attachments Upload an attachment
Upload a file as multipart/form-data using field `file`. Include the returned attachment id in message markdown as `attachment:<id>` to attach it to a message.
Scope attachments:write
Response 201
dataobjectrequiredidstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringrequiredcreatedAtstringrequiredStatus codes
201Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/attachments/search Search attachments
Search accessible attachments by filename or extracted content. Omit query to browse the most recent attachments.
Scope attachments:read
Request body
querystringstreamsstring[]contentTypesstring[]limitintegerrequiredResponse 200
dataobject[]requiredidstringrequiredfilenamestringrequiredmimeTypestringrequiredcontentTypestring | nullrequiredsummarystring | nullrequiredstreamIdstringmessageIdstringcreatedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/attachments/{attachmentId} Get an attachment
Retrieve attachment metadata and extracted content for an accessible attachment.
Scope attachments:read
Path parameters
attachmentIdstringrequiredAttachment ID (prefixed ULID)
Response 200
dataobjectrequiredidstringrequiredfilenamestringrequiredmimeTypestringrequiredsizeBytesintegerrequiredprocessingStatusstringrequiredcreatedAtstringrequiredextractionobject | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/attachments/{attachmentId}/url Get an attachment download URL
Create a short-lived signed URL for an accessible attachment.
Scope attachments:read
Path parameters
attachmentIdstringrequiredAttachment ID (prefixed ULID)
Response 200
dataobjectrequiredurlstringrequiredexpiresInintegerrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Users
List workspace users
/api/v1/workspaces/{workspaceId}/users List workspace users
List users in the workspace with optional text search and cursor pagination.
Scope users:read
Query parameters
querystringafterstringlimitintegerResponse 200
dataobject[]requiredidstringrequirednamestringrequiredslugstringrequiredemailstringrequiredavatarUrlstringrolestringrequiredhasMorebooleanrequiredcursorstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
Labels
List, create, edit, archive, join, and apply workspace labels
/api/v1/workspaces/{workspaceId}/labels List labels
The key actor's labels (every label is private to its owner) and their resource assignments.
Scope labels:read
Response 200
dataobjectrequiredlabelsobject[]requiredidstringrequiredworkspaceIdstringrequiredcreatorActorTypestringrequiredcreatorActorIdstringrequirednamestringrequiredslugstringrequiredcolorstringrequiredemojistring | nullrequireddescriptionstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredarchivedAtstring | nullrequiredassignmentsobject[]requiredlabelIdstringrequiredresourceTypestringrequiredresourceIdstringrequiredactorTypestringrequiredactorIdstringrequiredworkspaceIdstringrequiredassignedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/labels Create or update a label by name
Find-or-create a label owned by the key actor (a user or a bot), keyed by its name. Posting an existing name returns that label and applies any appearance fields supplied; labels are identified by their text, so this is idempotent.
Scope labels:write
Request body
namestringrequiredcolorstringemojistring | nulldescriptionstring | nullResponse 201
dataobjectrequiredidstringrequiredworkspaceIdstringrequiredcreatorActorTypestringrequiredcreatorActorIdstringrequirednamestringrequiredslugstringrequiredcolorstringrequiredemojistring | nullrequireddescriptionstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredarchivedAtstring | nullrequiredStatus codes
201Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/labels/assignments Apply a label to a resource by name
Attach a label to a resource the key actor can reach, identifying the label by its text: the label is found-or-created for the actor, then assigned. `resourceType` is the polymorphic target (`stream` today) so the same endpoint labels any future resource without a wire change.
Scope labels:write
Request body
namestringrequiredcolorstringemojistring | nulldescriptionstring | nullresourceTypestringrequiredresourceIdstringrequiredResponse 201
dataobjectrequiredlabelobjectrequiredidstringrequiredworkspaceIdstringrequiredcreatorActorTypestringrequiredcreatorActorIdstringrequirednamestringrequiredslugstringrequiredcolorstringrequiredemojistring | nullrequireddescriptionstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredarchivedAtstring | nullrequiredassignmentobjectrequiredlabelIdstringrequiredresourceTypestringrequiredresourceIdstringrequiredactorTypestringrequiredactorIdstringrequiredworkspaceIdstringrequiredassignedAtstringrequiredStatus codes
201Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/labels/assignments Remove a label from a resource by name
Remove the key actor's assignment of a label (identified by its text) from a resource.
Scope labels:write
Query parameters
namestringrequiredresourceTypestringrequiredresourceIdstringrequiredStatus codes
204No content400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/labels/{labelId} Update a label
Update a label the key actor created.
Scope labels:write
Path parameters
labelIdstringrequiredLabel ID (prefixed ULID)
Request body
namestringcolorstringemojistring | nulldescriptionstring | nullResponse 200
dataobjectrequiredidstringrequiredworkspaceIdstringrequiredcreatorActorTypestringrequiredcreatorActorIdstringrequirednamestringrequiredslugstringrequiredcolorstringrequiredemojistring | nullrequireddescriptionstring | nullrequiredcreatedAtstringrequiredupdatedAtstringrequiredarchivedAtstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/labels/{labelId} Delete a label
Archive a label the key actor created and remove its assignments.
Scope labels:write
Path parameters
labelIdstringrequiredLabel ID (prefixed ULID)
Status codes
204No content400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Bot runtimes
Register a runtime and keep its presence alive so it can be assigned work.
Recipes Connect your local agent →
/api/v1/workspaces/{workspaceId}/bot-runtime/presence Heartbeat bot runtime presence
Scope bot-runtime:write
Request body
runtimeKindstringrequiredinstanceIdstringrequiredruntimeSessionIdstringdisplayNamestringstatusstringrequiredacceptingInvocationsbooleanrequiredcapabilitiesobjectrequiredstatusTextstringpublicKeystringpublicKeyIdstringResponse 200
dataobjectrequiredidstringrequiredworkspaceIdstringrequiredbotIdstringrequiredruntimeKindstringrequiredinstanceIdstringrequireddisplayNamestring | nullrequiredstatusstringrequiredacceptingInvocationsbooleanrequiredcapabilitiesobjectrequiredstatusTextstring | nullrequiredlastSeenAtstringrequiredcreatedAtstringrequiredupdatedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/bot-runtime/sessions Create or link a bot runtime session
Scope bot-runtime:write
Request body
runtimeKindstringrequiredinstanceIdstringrequiredruntimeSessionIdstringrequireddisplayNamestringrequiredlocalCwdstringmemoryModestringlabelNamestringdescriptionstringe2eobjectownerKeyIdstringrequiredifArchivedstringifMissingstringResponse 200
dataobjectrequiredlinkIdstringrequiredrootStreamIdstringrequiredactiveStreamIdstringrequiredruntimeSessionIdstringrequiredstreamUrlPathstringrequirede2eEnabledbooleanStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/bot-runtime/owner-e2e-key Get the bot owner's active encryption public key
The bot owner's active UIK (key id + base64 X25519 public key). A sealed harness fetches this before creating an end-to-end-encrypted session so it can wrap the generation-0 stream key to the owner. 404 when the owner has not set up encryption.
Scope bot-runtime:write
Response 200
dataobjectrequiredkeyIdstringrequiredpublicKeystringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/streams/{streamId}/e2e/key-wraps Provision the generation-0 key wraps for a harness-created encrypted scratchpad
Phase two of harness-created E2E scratchpads: stores the stream-key wraps (owner UIK + the harness's own BIK) minted against the stream id returned by session create. Bot-actor-only, current generation only, and only while the generation has no wraps. Slots are immutable, so a replay cannot splice keys.
Scope bot-runtime:write
Path parameters
streamIdstringrequiredStream ID
Request body
keyGenerationintegerrequiredwrapsobject[]requiredrecipientKindstringrequiredrecipientKeyIdstringrequiredwrapEncstringrequiredwrapCtstringrequiredResponse 200
dataobjectrequiredstoredintegerrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-runtime/sessions/rename Rename the scratchpad linked to a bot runtime session
Scope bot-runtime:write
Request body
instanceIdstringrequiredruntimeSessionIdstringrequireddisplayNamestringrequiredResponse 200
dataobjectrequiredlinkIdstringrequiredrootStreamIdstringrequiredactiveStreamIdstringrequiredruntimeSessionIdstringrequiredstreamUrlPathstringrequirede2eEnabledbooleandisplayNamestringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-runtime/sessions/rebind Move an existing bot runtime session link to a new runtime instance id
Scope bot-runtime:write
Request body
linkIdstringrequiredinstanceIdstringrequiredruntimeSessionIdstringrequirednewInstanceIdstringrequiredResponse 200
dataobjectrequiredlinkIdstringrequiredrootStreamIdstringrequiredactiveStreamIdstringrequiredruntimeSessionIdstringrequiredstreamUrlPathstringrequirede2eEnabledbooleanStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Bot invocations
Claim, renew, step through, complete, or fail the work a bot is summoned to do.
Recipes Connect your local agent →
/api/v1/workspaces/{workspaceId}/bot-invocations/claim Claim one pending bot invocation
Scope bot-invocations:write
Request body
runtimeKindstringrequiredinstanceIdstringrequiredruntimeSessionIdstringsupportedCapabilitiesstring[]requiredclaimTtlSecondsintegerrequiredresponseStreamIdstringResponse 200
dataobject | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/renew Renew a claimed bot invocation
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
instanceIdstringrequiredclaimTokenstringrequiredclaimTtlSecondsintegerrequiredResponse 200
dataobjectrequiredinvocationIdstringrequiredstatusstringrequiredclaimExpiresAtstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/steps Record a bot invocation trace step
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
instanceIdstringrequiredclaimTokenstringrequiredstepTypestringrequiredcontentstringrequiredstatusTextstringclientStepIdstringResponse 200
dataobjectrequiredinvocationIdstringrequiredsessionIdstringrequiredstepIdstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/sealed-steps/started Open an in-flight sealed bot invocation trace step
Sealed variant of the trace-step start, for an owner-granted E2E bot harness: the content is ciphertext the server never decrypts. Authenticated with the per-claim callback token in the X-Threa-Callback-Token header.
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
stepIdstringrequiredstepTypestringrequiredmessageIdstringciphertextstringenvelopeobjectvnumberrequiredkeyGenerationintegerrequiredivstringrequiredaadstringrequiredResponse 200
dataobjectrequiredinvocationIdstringrequiredsessionIdstringrequiredstepIdstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/sealed-steps Finalize a sealed bot invocation trace step
Sealed variant of the trace-step finalize, for an owner-granted E2E bot harness: sets the sealed content + completion on the step opened at sealed-steps/started (or inserts a completed row if the start was dropped). Authenticated with the per-claim callback token in the X-Threa-Callback-Token header.
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
stepIdstringrequiredstepTypestringrequiredmessageIdstringciphertextstringrequiredenvelopeobjectrequiredvnumberrequiredkeyGenerationintegerrequiredivstringrequiredaadstringrequireddurationMsintegerResponse 200
dataobjectrequiredinvocationIdstringrequiredsessionIdstringrequiredstepIdstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/sealed-messages Post a sealed interim message from an in-flight sealed bot invocation
Sealed variant of a mid-turn bot message, for an owner-granted E2E bot harness: posts one sealed interim message (ciphertext the server never decrypts) into the claim's stream before the turn completes: progress notes, permission prompts, early acks. The client-minted messageId binds the seal AAD and dedupes retries. Authenticated with the per-claim callback token in the X-Threa-Callback-Token header.
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
messageIdstringrequiredciphertextstringrequiredenvelopeobjectrequiredvnumberrequiredkeyGenerationintegerrequiredivstringrequiredaadstringrequiredattachmentIdsstring[]Response 200
dataobjectrequiredmessageIdstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/sealed-complete Complete a sealed bot invocation
Sealed variant of the completion, for an owner-granted E2E bot harness: persists the turn's final sealed reply (ciphertext the server never decrypts) or noResponse, flips the claim, and finalizes the agent session. Authenticated with the per-claim callback token in the X-Threa-Callback-Token header.
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
replyobjectmessageIdstringrequiredciphertextstringrequiredenvelopeobjectrequiredvnumberrequiredkeyGenerationintegerrequiredivstringrequiredaadstringrequiredattachmentIdsstring[]noResponsebooleanResponse 200
dataobjectrequiredinvocationIdstringrequiredsessionIdstringrequiredmessageIdstring | nullrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/complete Complete a claimed bot invocation
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
instanceIdstringrequiredclaimTokenstringrequiredfinalMessageMarkdownstringnoResponsebooleansourcesobject[]typestringtitlestringrequiredurlstringrequiredsnippetstringmetadataobjectsealedReplyobjectmessageIdstringrequiredciphertextstringrequiredenvelopeobjectrequiredvnumberrequiredkeyGenerationintegerrequiredivstringrequiredaadstringrequiredResponse 200
dataobjectrequiredinvocationIdstringrequiredmessageobject | nullrequiredslotsobjectrequiredHydration for cross-stream shared-message pointers in the returned messages, keyed by `shared:<messageId>`. Always present; empty when no message references a shared source.
Status codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/bot-invocations/{invocationId}/fail Fail a claimed bot invocation
Scope bot-invocations:write
Path parameters
invocationIdstringrequiredInvocation ID
Request body
instanceIdstringrequiredclaimTokenstringrequirederrorMessagestringrequiredResponse 200
dataobjectrequiredinvocationIdstringrequiredstatusstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
Delegations
Close the loop on delegated tasks: your local agent lists the open queue, claims a task, reports progress, and completes it with a result posted back to the stream.
/api/v1/workspaces/{workspaceId}/delegations List open delegations
List delegated tasks that are open to claim, filtered to what the key can access: a user-scoped key sees streams its user can access, a workspace key sees the bot's channel grants.
Scope delegations:read
Query parameters
statusstringsincestringResponse 200
dataobject[]requiredidstringrequiredstreamIdstringrequiredtitlestringrequiredstatusstringrequiredclaimedByLabelstringstatusNotestringresultMessageIdstringsourceConversationIdstringcreatedAtstringrequiredstatusChangedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource
/api/v1/workspaces/{workspaceId}/delegations/{delegationId}/claim Claim an open delegation
Atomically claim an open delegation. Returns the brief, context refs, and the claim token (cleartext, exactly once; send it as X-Threa-Callback-Token on every later lifecycle call). A delegation that is no longer open returns 409.
Scope delegations:write
Path parameters
delegationIdstringrequiredDelegation ID (prefixed ULID)
Request body
claimedByLabelstringrequiredidempotencyKeystringResponse 200
dataobjectrequiredidstringrequiredstreamIdstringrequiredtitlestringrequiredstatusstringrequiredclaimedByLabelstringstatusNotestringresultMessageIdstringsourceConversationIdstringcreatedAtstringrequiredstatusChangedAtstringrequiredbriefstringrequiredcontextRefsstring[]requiredclaimTokenstringrequiredclaimExpiresAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found409Conflict: the resource is not in the state the operation requires
/api/v1/workspaces/{workspaceId}/delegations/{delegationId}/heartbeat Renew a delegation claim
Push the claim's expiry forward while the local agent is still working. Liveness only; no status change on the card. Authenticated with the per-claim token in the X-Threa-Callback-Token header.
Scope delegations:write
Path parameters
delegationIdstringrequiredDelegation ID (prefixed ULID)
Response 200
dataobjectrequiredclaimExpiresAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/delegations/{delegationId}/status Report delegation progress
Mark the delegation running and put a free-text progress note on its card (each report replaces the previous note; the claim TTL renews). Authenticated with the per-claim token in the X-Threa-Callback-Token header.
Scope delegations:write
Path parameters
delegationIdstringrequiredDelegation ID (prefixed ULID)
Request body
statusNotestringResponse 200
dataobjectrequiredidstringrequiredstreamIdstringrequiredtitlestringrequiredstatusstringrequiredclaimedByLabelstringstatusNotestringresultMessageIdstringsourceConversationIdstringcreatedAtstringrequiredstatusChangedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/delegations/{delegationId}/complete Complete a delegation
Complete the claimed delegation. When resultMarkdown is given, the result is posted in a thread anchored on the delegation card in the same transaction as the completion, authored as the key's user (with via-API provenance) for a user-scoped key, or as the bot for a workspace key. The response includes resultMessageId and resultThreadId. It enters the normal message pipeline, so workspace memory captures the outcome. Authenticated with the per-claim token in the X-Threa-Callback-Token header.
Scope delegations:write
Path parameters
delegationIdstringrequiredDelegation ID (prefixed ULID)
Request body
resultMarkdownstringmetadataobjectResponse 200
dataobjectrequiredidstringrequiredstreamIdstringrequiredtitlestringrequiredstatusstringrequiredclaimedByLabelstringstatusNotestringresultMessageIdstringsourceConversationIdstringcreatedAtstringrequiredstatusChangedAtstringrequiredresultThreadIdstringStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/delegations/{delegationId}/fail Fail a delegation
Mark the claimed delegation failed, recording why on its card. Authenticated with the per-claim token in the X-Threa-Callback-Token header.
Scope delegations:write
Path parameters
delegationIdstringrequiredDelegation ID (prefixed ULID)
Request body
errorMessagestringrequiredResponse 200
dataobjectrequiredidstringrequiredstreamIdstringrequiredtitlestringrequiredstatusstringrequiredclaimedByLabelstringstatusNotestringresultMessageIdstringsourceConversationIdstringcreatedAtstringrequiredstatusChangedAtstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found
/api/v1/workspaces/{workspaceId}/delegations/{delegationId}/request-access Request access to a delegation's stream
For a workspace (bot) key that received the delegation:available nudge but cannot claim it (no channel grant): file an access request that renders as a card in the delegation's stream for a member to approve or deny. Returns already_granted (no card) when the bot already has access; otherwise the request is idempotent per (bot, stream). 404 for an unknown delegation id — the existence-hiding carve-out is scoped to ids the workspace bot plane already saw on the nudge. A user-scoped key gets 400 (USER_KEY_CANNOT_REQUEST_ACCESS): a user key's access follows its user, who should join the stream directly.
Scope delegations:write
Path parameters
delegationIdstringrequiredDelegation ID (prefixed ULID)
Request body
requestedByLabelstringResponse 200
dataobjectrequiredrequestIdstringstatusstringrequiredStatus codes
200Successful response400Validation error401Missing or invalid API key403Insufficient permissions or inaccessible resource404Resource not found