# Discourse (/docs/integrations/discourse)

Connect a Discourse community to Sift to ingest topics, replies, and PMs into one inbox and reply from there - without your team having to live in Discourse.

## At a glance [#at-a-glance]

| | |
| --- | --- |
| Inbound: New topics (all monitored categories) | ✅ |
| Inbound: Topic replies + edits | ✅ |
| Inbound: Private messages to the agent user | ✅ |
| Inbound: Status changes (close / archive / trash) | ✅ |
| Inbound: Whispers / staff-only posts | ⚠️ visible only if the agent user can see them |
| Outbound: Post a reply in-thread | ✅ |
| Outbound: Close / reopen a topic | ✅ |
| Outbound: Flag a post for moderation | ✅ requires `post_actions` scope |
| Outbound: Edit / delete existing posts | ❌ never |
| Author enrichment (email, trust level, admin flag) | ✅ requires `check_emails` |
| Multi-Discourse instance per workspace | ✅ |
| Auto-reply bots | ✅ |
| Webhook-driven (no polling) | ✅ |

## Set it up [#set-it-up]

{/* setup-screenshot */}
<figure>
  <img src="/assets/screenshots/discourse/setup.png" alt="The Discourse settings page in Sift, showing connected accounts (account names redacted for the docs)." />
  <figcaption>Discourse settings in Sift - manage connected accounts here.</figcaption>
</figure>

Go to [Settings → Integrations → Discourse](https://app.getsift.ai/app/settings/integrations/) and:

1. **Create a dedicated "Sift agent" user** in Discourse - admin or moderator trust level. Sift scopes the API key to this single user, not "All Users."
2. **Generate an API key** scoped to that user, with the granular scopes in [API key setup](#api-key-setup) below.
3. **Add a webhook** in Discourse (Admin → API → Webhooks) pointing at `https://api.getsift.ai/integrations/discourse` with the events in [Webhook](#webhook) below.
4. **Paste the API key + community URL into Sift** and you're done - new topics start flowing in immediately.

The deep "what each scope is for" + "every endpoint we hit" details are below for your security review.

## What you can do [#what-you-can-do]

**Reply** in any topic from the inbox - Sift posts back to Discourse as the dedicated agent user. **Close or reopen** a topic from the inbox. **Flag a post for moderation** (spam / off-topic / inappropriate / notify-user / notify-moderators) if your API key has `post_actions` scope.

**Trigger workflows** off topic signals (category, author, trust level, keyword, sentiment). Route to the right team automatically. **Apply auto-reply bots** for FAQ patterns.

Author enrichment (email, trust level, admin flag) is layered onto every incoming post so workflows can route differently for staff vs. new-account vs. trust-level-3 users.

## API key setup [#api-key-setup]

- **Scope to a single dedicated user.** Create a "Sift agent" user in Discourse and scope the API key to that user only - **not** "All Users." Clean audit trail, single-account revocation.
- **Trust level.** The agent user must be **admin or moderator**. Required for `/admin/users/*`, topic status changes, and user creation regardless of granular scopes.
- **Attribution.** Outbound replies use the `Api-Username` header to attribute posts to the agent user (or `system` as fallback).

## Granular scopes [#granular-scopes]

The minimum set Sift needs:

```
categories:     list, show
topics:         read, read_lists, write, status
users:          list, show, check_emails, create
post_actions:   (governs POST /post_actions.json - see Flagging note below)
```

**`categories`**: `list` for the full category tree at setup + `show` to paginate topics within a category.

**`topics`**: `read` (every ingested topic + every webhook), `read_lists` (paginated category views), `write` (post agent replies), `status` (close/reopen). `update`/`delete`/`recover`/`change_owner` not used - do not grant.

**`users`**: `list` (connection check + find-by-email), `show` (enrich every author with email, trust level, admin flag), `check_emails` (Discourse hides emails behind this scope even for admin keys), `create` (one-time: provisions the Sift agent user during onboarding). Nothing else - no update, no log_out, no suspend, no delete, no sync_sso.

**`posts`**: none. Replies are covered by `topics:write`. Sift never edits, deletes, or recovers posts.

### Flagging - `post_actions` [#flagging---post_actions]

Sift calls `POST /post_actions.json` to flag posts for moderation (spam / off-topic / inappropriate / notify-user / notify-moderators) from the inbox. If `post_actions` isn't selectable as its own scope in your key UI, either grant whichever scope group covers `/post_actions.json` in your Discourse version, or ship without flagging - it's not on the critical path.

## What Sift never touches [#what-sift-never-touches]

No AI, automation, badges, data explorer, email-in, groups, invites, logs, revisions, search, solved, tags, uploads, user_status, or WordPress endpoints. Reach out if your security review wants the full enumerated allowlist.

## Webhook [#webhook]

In **Admin → API → Webhooks**, point at:

```
https://api.getsift.ai/integrations/discourse
```

Subscribe to: `topic_created`, `topic_edited`, `topic_archived`, `topic_closed`, `topic_recovered`, `topic_trashed`, `post_created`. Webhook secret recommended. Sift dedupes its own outbound posts (5-min TTL) so agent-reply echoes don't get re-ingested.

## Endpoint reference [#endpoint-reference]

For audit:

| Endpoint | Used for | Scope |
| --- | --- | --- |
| `GET /site.json` | Onboarding | public |
| `GET /categories.json` | Enumerate categories | `categories:list` |
| `GET /c/{slug}/{id}.json` | List topics | `categories:show` + `topics:read_lists` |
| `GET /t/{topicId}.json` | Full topic + posts | `topics:read` |
| `GET /admin/users/list/{filter}.json` | Connection check, find-by-email | `users:list` (+ `check_emails`) |
| `GET /admin/users/{id}.json` | User enrichment | `users:show` + `check_emails` |
| `GET /u/{username}.json` | Public user lookup | `users:show` |
| `POST /users.json` | Provision Sift agent user | `users:create` |
| `POST /posts.json` | Post agent reply | `topics:write` |
| `PUT /t/{topicId}/status.json` | Close / reopen topic | `topics:status` |
| `POST /post_actions.json` | Flag post | `post_actions:*` |
