Skip to main content
Version: main 🚧

Contract: Slack Identity Linking v1

API contract for the Slack identity linking flow (FR-025).

Contract overview

PropertyValue
Version1
StatusDraft
FR coverageFR-025, FR-032
ServicesUI BFF (Next.js), Slack bot (Python)

Endpoints

POST /api/auth/slack-link (BFF Callback)

Handles the OIDC authorization code exchange after the user clicks the linking URL.

Query parameters:

ParamTypeRequiredDescription
noncestringYesSingle-use linking nonce (10min TTL)
slack_user_idstringYesSlack user ID to link
codestringYesOIDC authorization code from Keycloak
statestringYesOIDC state parameter

Success response (302 redirect to success page):

  • Stores slack_user_id as Keycloak user attribute via Admin API
  • Posts confirmation DM to user via Slack Web API
  • Renders HTML success page

Error responses:

StatusCondition
400Nonce expired (>10min) or already consumed
400Missing required parameters
401OIDC code exchange failed
500Keycloak Admin API or Slack API error

GET /api/auth/my-roles (User Self-Service)

Returns the authenticated user's full RBAC posture (FR-036).

Authentication: Required (NextAuth session)

Response (200):

{
"email": "user@example.com",
"realm_roles": ["chat_user", "team_member", "kb_reader:kb-team-a"],
"teams": [
{ "id": "team-a-id", "name": "Team Alpha" }
],
"per_kb_roles": [
{ "kb_id": "kb-team-a", "kb_name": "Team A Docs", "scope": "reader" }
],
"per_agent_roles": [
{ "agent_id": "agent-123", "agent_name": "My Agent", "scope": "user" }
],
"idp_source": "duo-sso",
"slack_linked": true,
"slack_user_id": "U1234567"
}

Admin Slack Endpoints

GET /api/admin/slack/users (FR-032a)

Returns Slack user bootstrapping data for the admin dashboard.

Authentication: Required (admin role) Pagination: ?page=1&limit=20

Response (200):

{
"users": [
{
"slack_user_id": "U1234567",
"slack_display_name": "John Doe",
"link_status": "linked",
"keycloak_username": "jdoe",
"keycloak_sub": "user-uuid",
"roles": ["chat_user", "team_member"],
"teams": ["Team Alpha"],
"link_timestamp": "2026-03-25T10:00:00Z",
"last_bot_interaction": "2026-03-25T14:30:00Z",
"obo_success_count": 42,
"obo_fail_count": 1,
"active_channels": ["C1234", "C5678"]
}
],
"total": 25,
"page": 1,
"limit": 20
}

GET /api/admin/slack/channel-mappings (FR-032b)

Returns channel-to-team mappings.

Authentication: Required (admin role)

Response (200):

{
"mappings": [
{
"_id": "mapping-id",
"slack_channel_id": "C1234567",
"channel_name": "#team-a-general",
"team_id": "team-a-id",
"team_name": "Team Alpha",
"created_by": "admin@example.com",
"created_at": "2026-03-25T10:00:00Z",
"active": true
}
]
}

POST /api/admin/slack/channel-mappings

Creates a new channel-to-team mapping.

Request body:

{
"slack_channel_id": "C1234567",
"team_id": "team-a-id"
}

DELETE /api/admin/slack/channel-mappings/[id]

Removes a channel-to-team mapping.