# Channels

> The Channels operations of the Hub REST API (8 operations).

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

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

## GET /channels

List/search channels

Returns channels the authenticated user has access to. Supports optional workspace and type filters. When `q` is set the SAME query is narrowed server-side by title / branch purpose — browse and search are one code path over one access floor, never two loaders.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `userId` | query | string |  |  |
| `workspaceId` | query | string |  |  |
| `channelType` | query | string |  |  |
| `contextObjectId` | query | string |  |  |
| `q` | query | string |  | Free-text search over channel title / branch purpose. |
| `limit` | query | string |  |  |
| `offset` | query | string |  | Zero-based offset for cursor-free pagination (default 0). |

Responses:

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

## POST /channels

Create-or-link an EXTERNAL channel bound to an entity

Idempotent find-or-link of the canonical EXTERNAL channel for (externalSource, externalChannelId). Creates the channel if absent and, when contextObjectId is supplied, binds it to that entity (e.g. a client person/client entity mirrored as a Discord channel). Reuses the shared inbound-recorder upsert so it dedups on the (externalSource, externalId) partial unique index.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `workspaceId` | string |  |  |
| `externalSource` | string | yes |  |
| `externalChannelId` | string | yes |  |
| `contextObjectId` | string |  |  |
| `contextObjectType` | "entity" \| "document" \| "view" \| "proposal" |  |  |
| `title` | string |  |  |
| `parentChannelId` | string |  |  |
| `branchPurpose` | string |  |  |
| `relink` | boolean |  |  |
| `metadata` | object |  |  |

Responses:

- `200`: Created-or-linked channel
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /channels/by-context

Find or create a channel by context object

Resolves (or creates) the AI channel scoped to a specific entity, document, or view.

Request body (`application/json`): ChannelByContextRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `userId` | string | yes |  |
| `workspaceId` | string |  |  |
| `contextObjectId` | string | yes |  |
| `contextObjectType` | "entity" \| "document" \| "view" | yes |  |

Responses:

- `200`: Resolved channel
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## GET /channels/personal

Get the user's personal channel

Returns the per-(user, workspace) personal AI channel, scoped to the orchestrator agent.

Parameters:

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

Responses:

- `200`: Channel
- `400`: Bad request
- `403`: Forbidden
- `404`: Orchestrator agent not found
- `500`: Internal error

## POST /channels/trigger-ai

Trigger an AI response in a channel

Posts a system-prompt-overridden message into the channel and dispatches the AI to respond. Used by skill triggers and proactive entry points.

Request body (`application/json`): TriggerAiRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `channelId` | string | yes |  |
| `userId` | string | yes |  |
| `workspaceId` | string | yes |  |
| `systemPromptOverride` | string | yes |  |
| `skillId` | string |  |  |
| `entityId` | string |  | Entity to inject as context for the AI response. |

Responses:

- `200`: Trigger result (varies by skill/agent)
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /channels/:externalChannelId/pins/:messageId

Pin a message in a Discord channel

Resolves the EXTERNAL channel by (externalSource='discord', externalId=:externalChannelId) and ENQUEUES an agnostic `pin_message` egress intent; the bridge performs the Discord pin.

Responses:

- `200`: Queued
- `400`: Bad request
- `403`: Forbidden
- `404`: Channel not found
- `500`: Internal error

## POST /channels/:externalChannelId/rename

Rename a Discord channel

Resolves the EXTERNAL channel by (externalSource=provider||'discord', externalId=:externalChannelId) and ENQUEUES an agnostic `rename_channel` egress intent; the bridge performs the Discord rename (rate-limited ~2 renames per 10 min per channel).

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes |  |
| `provider` | string |  |  |

Responses:

- `200`: Queued
- `400`: Bad request
- `403`: Forbidden
- `404`: Channel not found
- `500`: Internal error

## POST /channels/:channelId/bind

Propose binding a channel to a context object

Files a governed `channel/bind` proposal (subjectType 'channel', action 'bind'). On approval, points an EXISTING channel (by Synap channel UUID) at a context object — usually a client entity — and optionally stamps the firewall role (branchPurpose). ALWAYS proposes unless a workspace opted 'channel.bind' into autoApproveFor. branchPurpose is human-confirmed, never default-forced (client-comms is immutable once set).

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `userId` | string | yes |  |
| `workspaceId` | string |  |  |
| `contextObjectId` | string | yes |  |
| `contextObjectType` | "entity" \| "document" \| "view" |  |  |
| `branchPurpose` | string |  |  |
| `externalChannelId` | string |  |  |
| `reasoning` | string |  |  |

Responses:

- `200`: Proposed / approved / denied
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error
