> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/developer-guides/api-reference/data-collaboration.md).

# Data Collaboration

The Data Collaboration HTTP surface is split across the data-collaboration controllers (`DataCollaborationController`, `EventApiController`) and a single Slack-events webhook. Every route below is gated by `@ConditionalOnDataCollaboration` (the Slack webhook controller is also conditional) — they return `404 Not Found` when `datacollaboration.enabled=false`. For the configuration keys (`datacollaboration.*`), the Slack app manifest, and the operator setup flow, see [Configure ODD Platform → Enable Data Collaboration](/configuration-and-deployment/odd-platform.md#enable-data-collaboration); for the user-facing description (the per-entity **Discussions** tab, how a discussion flows, the message-lifecycle model), see [Active platform features → Data Collaboration](/features/active-platform-features/data-collaboration.md). The Slack integration here is a **full Slack app** (OAuth-token-driven; the [Slack Events API](https://docs.slack.dev/apis/events-api/) webhook reads replies back into the platform) — **distinct** from the [outgoing alert webhook](/configuration-and-deployment/odd-platform.md#enable-alert-notifications) used by alert notifications (`notifications.receivers.slack.url`, one-way write only).

## Authentication and authorization

Read these caveats before exposing any Data Collaboration route, especially on a public-facing or `auth.type=DISABLED` deployment.

{% hint style="danger" %}
**`POST /api/slack/events` is unauthenticated and unsigned — any network caller can forge an event the platform treats as a genuine Slack reply.** The path is on the platform's authentication allow-list (`SecurityConstants.WHITELIST_PATHS` → `permitAll()` in `AuthorizationCustomizer`), so it is reachable without a session under every `auth.type`. The handler reads the request body as a raw `Mono<String>` and dispatches it to the Slack event parser without reading the `X-Slack-Signature` / `X-Slack-Request-Timestamp` headers — a code-side search for `X-Slack-Signature`, `signingSecret`, `verifySignature`, and `HmacSHA256` returns zero matches, so Slack's mandated HMAC-SHA256 request-signature check is not performed. A forged `event_callback` materialises arbitrary messages in the per-entity **Discussions** tab and runs the same downstream processing as a real delivery. Put a reverse proxy that performs Slack HMAC-SHA256 verification in front of `/api/slack/events` until the platform-side check lands.
{% endhint %}

{% hint style="warning" %}
**None of the Data Collaboration routes carry an RBAC permission gate — `datacollaboration.enabled` is the only access control.** The platform's security-rule registry has no entry for `/api/datacollaboration/...`, `/api/dataentities/{id}/messages`, or `/api/messages/...`, so every route except the allow-listed webhook falls through to the global "any authenticated user" rule — no `TERM_*`-style permission applies. Under `auth.type=DISABLED` the global rule is `permitAll()`, so **enabling Data Collaboration on a DISABLED-auth deployment publishes the entire read-and-post surface to anonymous callers**, not just the webhook. Do not enable Data Collaboration on an internet-reachable deployment without a real authentication mode (`LOGIN_FORM`, `OAUTH2`, or `LDAP`) plus the perimeter signature check above.
{% endhint %}

**Outbound to the provider — read & post**

| Method | Path                                              | Operation ID         | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------ | ------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET`  | `/api/datacollaboration/providers/slack/channels` | `getSlackChannels`   | List Slack channels the bot can write to, optionally filtered by `channel_name`. Used by the in-app channel autocomplete. The `channel_name` filter is a **prefix** match (`startsWith`), not a substring match — typing the middle of a channel name returns nothing. Results come from a Caffeine cache with a 60-second write expiry, so a bot freshly added to a channel will not appear until the cache refreshes. |
| `POST` | `/api/datacollaboration/providers/slack/messages` | `postMessageInSlack` | Queue a message for delivery into Slack. Returns `202 Accepted` once the message is enqueued; a background sender (`DataCollaborationMessageSenderJob`) drains the queue with up to `datacollaboration.sending-messages-retry-count` retries per message.                                                                                                                                                               |

**Per-entity threads & history**

| Method | Path                                                       | Operation ID            | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------ | ---------------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET`  | `/api/dataentities/{data_entity_id}/messages`              | `getDataEntityMessages` | List thread-root messages attached to a data entity, paged by `size` and (optionally) filtered by `channel_id`.                                                                                                                                                                                                                                                                                                                                                                                                  |
| `GET`  | `/api/dataentities/{data_entity_id}/messages/{message_id}` | `getMessages`           | List replies under a specific parent message, paged by `size` from `last_message_id`.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `GET`  | `/api/dataentities/{data_entity_id}/channels`              | `getChannels`           | Top channels that already carry messages about the given entity (used to seed the channel picker).                                                                                                                                                                                                                                                                                                                                                                                                               |
| `GET`  | `/api/messages/{message_id}/url`                           | `redirect`              | `302 Found` redirect to the provider's deep-link for the message — runtime returns `HttpStatus.FOUND` from `DataCollaborationController.redirect(...)` via `DataCollaborationService.resolveMessageUrl(...)`. **Spec / runtime drift:** the OpenAPI spec declares `301 Moved Permanently` for this route; the platform actually serves `302 Found`. Operators relying on cache-cacheability semantics should treat the response as `302` for now (clients that follow redirects unconditionally are unaffected). |

**Inbound webhook from Slack**

| Method | Path                | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST` | `/api/slack/events` | Slack [Events API](https://docs.slack.dev/apis/events-api/) webhook. Handles three event types in `SlackEventParser`: **`url_verification`** (Slack handshake — returns the `challenge` token), **`event_callback`** (a thread reply — enqueued via `DataCollaborationService.enqueueMessageEvent(...)` and translated to a platform message by `DataCollaborationMessageEventProcessor`), and **filter / error** events (logged, acknowledged with 200 / 400). The path is fixed in `EventApiController.java:22`, so the manifest line `https://<ODD_PLATFORM_BASE_URL>/api/slack/events` matches the platform's route exactly. |

## See also

* [API Reference hub](/developer-guides/api-reference.md) — the full per-feature index.
* [Active platform features → Data Collaboration](/features/active-platform-features/data-collaboration.md) — feature description, Discussions tab, message-lifecycle model.
* [Configure ODD Platform → Enable Data Collaboration](/configuration-and-deployment/odd-platform.md#enable-data-collaboration) — `datacollaboration.*` configuration keys + Slack app manifest.
