# Threads

> The Threads operations of the Hub REST API (10 operations).

URL: https://docs.synap.live/docs/reference/hub-api/threads · Generated from Synap-core/backend@c31e261ab (main)

Paths are relative to `/api/hub` on a pod.

## GET /threads

List chat threads for a user

Returns threads owned by `userId`, scoped to either the requested `workspaceId` or all of the user's accessible workspaces (plus pod-wide personal/feed channels).

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `userId` | query | string | yes |  |
| `workspaceId` | query | string |  |  |
| `limit` | query | string |  |  |

Responses:

- `200`: Threads list
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /threads

Create or upsert a thread

Creates a new thread, OR upserts on (externalSource, externalId) when both are provided. Race-safe via partial unique index. Supports `Idempotency-Key`.

Request body (`application/json`): CreateThreadRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `userId` | string | yes |  |
| `workspaceId` | string | yes |  |
| `title` | string |  |  |
| `parentChannelId` | string |  |  |
| `agentId` | string |  |  |
| `branchPurpose` | string |  |  |
| `contextObjectType` | string |  |  |
| `contextObjectId` | string |  |  |
| `externalSource` | string |  |  |
| `externalId` | string |  |  |

Responses:

- `200`: Thread created or reused
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## GET /threads/{threadId}/context

Get thread context (summary + linked entities/documents)

Responses:

- `200`: Success
- `401`: Unauthorized
- `403`: Forbidden — missing scope
- `500`: Internal error

## PATCH /threads/{threadId}/context

Update thread context summary

Responses:

- `200`: Success
- `401`: Unauthorized
- `403`: Forbidden — missing scope
- `500`: Internal error

## POST /threads/{threadId}/link-entity

Link an entity to a thread

Responses:

- `200`: Success
- `401`: Unauthorized
- `403`: Forbidden — missing scope
- `500`: Internal error

## POST /threads/{threadId}/link-document

Link a document to a thread

Responses:

- `200`: Success
- `401`: Unauthorized
- `403`: Forbidden — missing scope
- `500`: Internal error

## GET /threads/{threadId}/branches

List active branches under a thread

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `threadId` | path | string | yes |  |

Responses:

- `200`: Branches
- `403`: Forbidden
- `500`: Internal error

## GET /threads/{threadId}/messages

List messages in a thread

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `threadId` | path | string | yes |  |

Responses:

- `200`: Messages
- `403`: Forbidden
- `500`: Internal error

## POST /threads/{threadId}/messages

Append a single message to a thread

Inserts one message. When `autoRespond=true` and `role=user`, schedules an A2AI agent reply via pg-boss. Supports `Idempotency-Key`.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `threadId` | path | string | yes |  |

Request body (`application/json`): PostMessageRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `role` | "system" \| "assistant" \| "user" | yes |  |
| `content` | string | yes |  |
| `userId` | string | yes |  |
| `metadata` | object |  |  |
| `autoRespond` | boolean |  | If true and role=user, schedules an A2AI agent reply via pg-boss. |
| `kind` | "question" \| "update" |  | An AGENT's post only: 'question' when the person is needed (in a session room it pushes them, once per session window); 'update' (default) for progress/results — in-app only, never a push. |
| `slotLabel` | string |  | With kind 'question' (agent posts only): the label of the session output the question is about. The question is filed ON that output (the owner's, with the question as its why), and the owner's answer is recorded as its `answer` and hands it back to the agent. |
| `ask` | object \| object \| object \| object \| object |  | With kind 'question' + slotLabel (agent posts only): HOW the person answers — the output's typed ask (confirm \| choose \| form \| act \| provide). Omit for a free-text answer. |

Responses:

- `200`: Inserted message id
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /threads/{threadId}/messages.batch

Append up to 100 messages atomically

All inserts run in a single Drizzle transaction. autoRespond fires for AT MOST ONE message — the LAST user message in the batch. Supports `Idempotency-Key`.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `threadId` | path | string | yes |  |

Request body (`application/json`): PostMessageBatchRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `messages` | array<object> | yes |  |
| `autoRespond` | boolean |  | If true, the LAST user message in the batch fires an A2AI reply. |

Responses:

- `200`: Inserted message ids
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error
