# Discord

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

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

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

## POST /discord/ingest

Record an inbound Discord message (no IS turn)

Dedup-records the inbound message and fires external_message.received. Does NOT dispatch the orchestrator. Use this for pure automation pipelines where no AI reply is needed.

Request body (`application/json`): DiscordIngestRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `workspaceId` | string |  |  |
| `discordChannelId` | string | yes |  |
| `guildId` | string |  |  |
| `discordUserId` | string | yes |  |
| `discordUsername` | string |  |  |
| `channelKind` | "dm" \| "guild" |  |  |
| `text` | string |  |  |
| `messageId` | string | yes |  |
| `attachments` | array<object> |  |  |

Responses:

- `200`: Record result
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /discord/agent-turn

Run one orchestrator turn for an inbound Discord message

Resolves-or-creates the Discord-bound channel, records the inbound message, runs ONE Intelligence Service orchestrator turn via streaming accumulation (external actions stay proposal-gated), and returns the agent's reply text synchronously. On pod deadline, returns partial:true with steps collected so far.

Request body (`application/json`): DiscordAgentTurnRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `workspaceId` | string |  |  |
| `discordChannelId` | string | yes |  |
| `guildId` | string |  |  |
| `discordUserId` | string | yes |  |
| `discordUsername` | string | yes |  |
| `channelKind` | "dm" \| "guild" |  |  |
| `text` | string |  |  |
| `messageId` | string | yes |  |
| `attachments` | array<object> |  |  |
| `skillName` | string |  |  |
| `priority` | "interactive" \| "background" |  |  |

Responses:

- `200`: Agent reply (may be partial with steps when timed out mid-stream)
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /discord/mail-feed/run

Run the Gmail mail feed now (on-demand)

Invokes the mail-feed runner in-process (gmail_search → triage → post into the Discord-bound channel) and returns the run summary. No-ops when the Discord tool's mailFeed is disabled. Idempotent via the watermark.

Responses:

- `200`: Run summary
- `403`: Forbidden
- `500`: Internal error

## POST /discord/reconcile-podwide

Migrate existing UNBOUND bridge channels to pod-wide (dry-run by default)

Flips workspace-pinned, UNBOUND bridge channels (external_source in discord/telegram, no context binding) to pod-wide (workspace_id NULL, scope 'pod') for the acting user. Dry-run unless `apply:true`. Client-BOUND channels are intentionally excluded (client-isolation firewall). Idempotent.

Request body (`application/json`): ReconcilePodwideRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `apply` | boolean |  |  |

Responses:

- `200`: Reconcile summary
- `403`: Forbidden
- `500`: Internal error

## POST /discord/event-sync/run

Run the Google Calendar event sync now (on-demand)

Invokes the event-sync runner in-process (event entities + stellar deadlines + calendar_list → native Discord scheduled events) and returns the run summary. No-ops when the Discord tool's eventSync is disabled. Idempotent via the dedup map.

Responses:

- `200`: Run summary
- `403`: Forbidden
- `500`: Internal error

## GET /discord/identity

Resolve an existing Discord→Synap identity link (read-only)

Returns whether the given Discord user is linked to a Synap user under the calling operator key. NEVER auto-creates a link.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `discordUserId` | query | string | yes |  |

Responses:

- `200`: Link status
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## GET /discord/identity/members

List linkable members of the acting workspace

Returns the human members of the acting (operator) workspace that a Discord user can be linked to via the onboarding picker.

Parameters:

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

Responses:

- `200`: Linkable members
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /discord/identity/link

Link a Discord user to a Synap identity (admin-gated)

Creates the Discord→Synap mapping. mode='existing' maps onto an existing workspace member (impersonation — admin-only). mode='new' provisions a fresh Synap user + team membership (account creation — admin-only). Authorized by the acting context's workspace role (owner/admin), never a caller-supplied flag.

Request body (`application/json`): DiscordIdentityLinkRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `discordUserId` | string | yes |  |
| `mode` | "existing" \| "new" | yes |  |
| `targetUserId` | string |  |  |
| `displayName` | string |  |  |
| `workspaceId` | string |  |  |

Responses:

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