Skip to main content

Slack

Slack Integration

SlackService → SlackAdapter → Slack Web API

OAuth 2.0 · User Token (xoxp-…) primary · Bot Token (xoxb-…) optional
🐙
SlackService
Business logic
Method routing
🔌
SlackAdapter
HTTP calls
Cursor pagination
💬
Slack Web API
slack.com/api/*
User Token (xoxp-…) — primary
Acts asThe authenticated person
SeesChannels, private, DMs
UnlocksDMs + search.messages
Invite neededNo — already a member
Bot Token (xoxb-…) — optional
Acts asThe app itself
SeesOnly channels it joined
CannotRead DMs or search
Use forsend_as=bot posts
Automatic Token Routing
D… (DM)User token — required
G… (private)User token — required
C… (public)User, bot as fallback
Missing tokenFails, never downgrades

What & Why

The Slack integration reads and writes Slack as the authenticated person, not as a bot. OAuth requests a user token (xoxp-…) alongside the optional bot token, so Octopus sees every conversation the user can see — public channels, private channels, DMs and group DMs — with no per-channel bot invite, and posts as them rather than as an app.

Every call flows through SlackAdapter, which picks the right token, handles cursor-based pagination, and surfaces Slack error codes as structured exceptions.

Three properties drive most deployments:

Complete visibility. A bot token only sees channels it was explicitly invited to and can never see DMs. The user token covers the person's whole Slack surface the moment they connect, which is what makes assistant-style use cases work at all.

Acting as the user. send_message posts under the person's own name by default. Pass send_as="bot" when you want the message to come from the app instead — for CI notifications, for example.

Search. search_messages maps to search.messages, which Slack exposes only to user tokens. It is the fastest way to answer "what was said about X" across every conversation at once.

Token routing is automatic

The conversation ID prefix decides the token: D… (DM) and G… (private channel or group DM) require the user token, C… (public channel) prefers it and falls back to the bot. When an action needs the user token and none is stored, the API returns slack_reconnect_required rather than silently downgrading — falling back would read the wrong thing or post as the wrong identity.

Which Token Is Used

Resolved from the conversation ID — callers never declare it
💬
Direct Message
D…
User token required
A bot token can never read or send a DM, so there is no fallback.
🔒
Private / Group
G…
User token required
The bot would need an explicit invite; the user is already a member.
📢
Public Channel
C…
User token, bot fallback
Bot-only installs auto-join the channel first; user tokens skip that.
Writes default to send_as="user". Slack only lets a message's original author edit or delete it, so update_message and delete_message take the same argument and it must match whoever sent it.

Methods Reference

MethodCategoryDescriptionKey Params
list_chatsConversationsChannels and DMs unified, most recent firstlimit, include_direct_messages, include_channels, unread_only
list_channelsChannelsList channels the token can seelimit, exclude_archived, types, cursor, team_id
get_channelChannelsFetch metadata for one channelchannel_id, include_num_members
list_channel_membersChannelsMember user IDs for a channelchannel_id, limit
list_messagesMessagesHistory for any conversation — channel, private, DMchannel_id, limit, oldest, latest, cursor, inclusive
get_messageMessagesFetch one message by timestampchannel_id, message_ts
list_thread_repliesMessagesReplies in a threadchannel_id, thread_ts, limit, oldest, latest, cursor, inclusive
search_messagesSearchFull-text search across channels and DMsquery, max_results, page, sort, sort_dir
send_messageMessagesPost to a channel or DMchannel_id, text, thread_ts, blocks, attachments, send_as
update_messageMessagesEdit a message you sentchannel_id, message_ts, text, blocks, send_as
delete_messageMessagesDelete a message you sentchannel_id, message_ts, send_as
list_usersUsersList workspace memberslimit
get_userUsersFetch a user profile by IDuser_id
get_connection_infoConnectionAuthenticated user ID and team ID

Required Slack App Scopes

Add these in your Slack app under OAuth & Permissions. The user scopes are what deliver DM access, full history and search.

User Token Scopes

ScopeUnlocks
chat:writeSend, edit and delete messages as the user
channels:history / channels:readPublic channel messages and metadata
groups:history / groups:readPrivate channels the user belongs to
im:history / im:read / im:writeDirect messages
mpim:history / mpim:read / mpim:writeGroup direct messages
users:read / users:read.emailResolve senders to names and emails
search:readsearch_messages
files:read / files:writeRead and upload attachments
reactions:readReactions on messages

Bot Token Scopes

Only needed if the app should also act as itself (send_as="bot") or operate without a signed-in user.

chat:write, channels:history, channels:read, groups:history, groups:read, im:history, im:read, mpim:history, mpim:read, users:read, users:read.email, channels:join

Existing connections must reconnect

Workspaces connected before user tokens were requested have a bot token only. DM, private-channel and search calls will return slack_reconnect_required until the user re-runs the OAuth flow.

HTTP Example

POST /slack/send-message

Send a direct message as the authenticated user.

Request
{
"channel_id": "D01ABCDEF",
"text": "Deploy complete",
"send_as": "user",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":white_check_mark: *Deploy complete*\nAll services healthy."
}
}
]
}
Response
{
"id": "1747634400.000100",
"provider": "slack",
"channel_id": "D01ABCDEF",
"text": "Deploy complete",
"sender": { "id": "U9876543210", "name": "Alice" },
"is_own_message": true,
"sent_at": "2026-05-19T09:20:00Z"
}
Omitting send_as posts as the user. Pass "bot" to post as the app — not valid for DMs. The returned id is the message timestamp; pass it as thread_ts to reply in a thread.

CLI Examples

# List every conversation, channels and DMs together
constellation slack list-chats --limit 20

# Read a DM thread (D… id — the user token is selected automatically)
constellation slack list-messages --channel-id D01ABCDEF --limit 50

# Read a public channel
constellation slack list-messages --channel-id C0123456 --limit 50

# Everything sent on one day (oldest/latest also take Slack ts or epoch seconds)
constellation slack list-messages --channel-id C0123456 --oldest 2026-01-01 --latest 2026-01-02

# Everything before a specific message — a message id is itself a valid bound
constellation slack list-messages --channel-id D01ABCDEF --latest 1747634400.000100

# ...and everything after it, including the message itself
constellation slack list-messages --channel-id D01ABCDEF --oldest 1747634400.000100 --inclusive

# Walk a long thread page by page (pass next_cursor back as --cursor)
constellation slack list-thread-replies --channel-id C0123456 --thread-ts 1747634400.000100 --limit 100

# Search everything the user can see
constellation slack search-messages --query "deployment failed"

# Search with Slack modifiers
constellation slack search-messages --query "from:@alice in:#engineering after:2026-01-01"

# Send a DM as yourself
constellation slack send-message --channel-id D01ABCDEF --text "Morning!"

# Post to a channel as the app instead of as yourself
constellation slack send-message --channel-id C0123456 --text "Build passed" --send-as bot

# Reply in a thread
constellation slack send-message --channel-id C0123456 --text "Reply" --thread-ts 1747634400.000100

# Edit a message you sent
constellation slack update-message --channel-id C0123456 --message-ts 1747634400.000100 --text "Deploy complete ✓"

# Delete a message you sent
constellation slack delete-message --channel-id C0123456 --message-ts 1747634400.000100

# Thread replies
constellation slack list-thread-replies --channel-id C0123456 --thread-ts 1747634400.000100

# Channels and members
constellation slack list-channels --limit 50
constellation slack list-channel-members --channel-id C0123456

# Who am I connected as
constellation slack get-connection-info