Connectors
/
Slack
Live · 18 tools

Slack Integration for AI Agents

The Slack API is approachable. Getting your agent to act on behalf of real users — across multiple workspaces, with the right token type and no stale credentials — is where the afternoon becomes a week.
Slack
Live

Messaging

Collaboration

Status
Live
Tools
18 pre-built
Auth
OAuth 2.0
Credential storage
Zero
Sandbox support
No

OAuth 2.0

Token rotation support

Zero credential storage

Revocation detection

User + bot token

The real problem

Why this is harder than it looks

Slack's API is genuinely well-built and the docs are good. The OAuth flow is straightforward to read about. The problems start when you sit down to connect a real agent that acts on behalf of real users across multiple workspaces.

The first thing most developers miss is that Slack's OAuth flow produces two fundamentally different token types, and your agent probably needs both. A bot token (xoxb-) represents your app's independent identity in a workspace — useful for sending notifications or managing channels programmatically. A user token (xoxp-) represents a specific human — needed when your agent should act as that person: sending messages in their name, reading their DMs, or setting their status. These require separate scope declarations in your app config (scope for bot, user_scope for user), separate token storage, and separate handling in your code. Conflating them produces silent failures where your agent either lacks permission or posts from the wrong identity entirely.

Then there's token rotation. By default, Slack tokens don't expire — but once you enable token rotation (which Slack now recommends and requires for distributed apps), every access token expires after 12 hours. Refresh tokens also rotate on use: each time you exchange a refresh token, the old one is revoked and a new one is issued. If two concurrent requests attempt to refresh the same token, you hit Slack's limit of 2 active tokens and the oldest is revoked. Building refresh logic that's safe under concurrency — and that stores the new refresh token atomically after each rotation — is non-trivial. Getting it wrong means users silently lose access and have to re-authorize from scratch.

On top of that: Slack's scope model is granular in ways that catch people off guard. channels:history reads public channel messages, but groups:history is a separate scope for private channels, and mpim:history covers multi-person DMs. Ask for the wrong one and you get a missing_scope error at runtime — not an auth error — which looks identical to a data access problem during debugging. Scalekit handles token type selection, rotation logic, scope configuration, and revocation detection so your code only has to call the tools.

Capabilities

What your agent can do with Slack

Once connected, your agent has 18 pre-built tools covering messaging, channels, users, and reactions:

  • Send and manage messages: post to channels or DMs, reply in threads, update or delete sent messages
  • Read conversation history: fetch channel messages with time-range filtering and pagination, retrieve full thread replies
  • Manage channels: list, create, join, leave, and invite users to public and private channels
  • Look up users: get user profiles by ID or email, check presence status, set user status
  • React and pin: add emoji reactions to messages, pin important messages to channels
Setup context

What we're building

This guide connects a team communication agent to Slack — helping users send updates, surface messages, manage channels, and act on conversations without leaving your product.

🤖
Example agent
Comms assistant sending messages and reading channel history on behalf of each user
🔐
Auth model
B2B SaaS — each user connects their own workspace. identifier = your user ID
⚙️
Scalekit account
app.scalekit.com — Client ID, Secret, Env URL
🔑
Slack app
Register at api.slack.com/apps — required for BYOC setup
Setup

1 Setup: One SDK, One credential

Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Slack secrets, no user tokens, nothing belonging to your customers.

pip install scalekit-sdk-python
import scalekit.client import os from dotenv import load_dotenv load_dotenv() scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) actions = scalekit.actions
Connected Accounts

2 Per-User Auth: Creating connected accounts

Each user gets their own Connected Account, giving them a dedicated auth context. The identifier is any unique string from your system — a UUID, email, whatever you use internally.

response = actions.get_or_create_connected_account( connection_name="slack", identifier="user_slack_123" # your internal user ID ) connected_account = response.connected_account print(f"Status: {connected_account.status}") # Status: PENDING — user hasn't authorized yet

This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.

Authorization Flow

3 The authorization flow

The user authorizes your agent once through Slack's native OAuth consent screen. Scalekit generates the URL with correct scopes, PKCE challenge, and redirect handling pre-configured. After approval, you never see the token.

if connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name="slack", identifier="user_slack_123" ) # Redirect user → Slack consent screen # Scalekit captures the token on callback return redirect(link.link)
Token management is automatic
After the user approves, Scalekit stores encrypted tokens and the connected account moves to ACTIVE. If token rotation is enabled on your Slack app, Scalekit handles the rotation cycle — including storing the new refresh token issued on each exchange. If a user revokes access from Slack's app settings, the account moves to REVOKED — no silent failures. Check account.status before critical operations.
Bring Your Own Credentials — required for Slack
Slack requires you to register your own app at api.slack.com/apps. Create the app, paste the Scalekit redirect URI into OAuth & Permissions → Redirect URLs, enable distribution under Manage Distribution, then copy your Client ID and Client Secret into the Scalekit dashboard. Token management stays fully handled.
Already have credentials?
Calling Slack

4 Calling Slack: What your agent writes

With the connected account active, your agent calls Slack tools using execute_tool. Name the tool, pass parameters. Scalekit handles token retrieval and request construction.

Send a message

Post to a channel or DM by channel ID or name. Supports plain text, Block Kit blocks, and thread replies via thread_ts.

result = actions.execute_tool( identifier="user_slack_123", tool_name="slack_send_message", tool_input={ "channel": "#engineering", "text": "Deployment to production completed. All health checks passing.", } ) # Returns the message timestamp (ts) — save it to update or reply later

Fetch channel history

Read recent messages from any channel the user has access to. Use oldest and latest to scope the time range.

result = actions.execute_tool( identifier="user_slack_123", tool_name="slack_fetch_conversation_history", tool_input={ "channel": "C0123456789", # channel ID "limit": 20, "oldest": "1700000000" # Unix timestamp } ) # { "messages": [ { "type": "message", "text": "...", "ts": "..." }, ... ] }

Look up a user by email

Resolve a user's Slack ID from their email address — useful when your system has the email but needs the Slack user ID to @mention or DM them.

result = actions.execute_tool( identifier="user_slack_123", tool_name="slack_lookup_user_by_email", tool_input={ "email": "jane.smith@acmecorp.com" } ) # { "user": { "id": "U0123456", "name": "jane.smith", "profile": {...} } }

Reply in a thread and add a reaction

Reply to an existing message by passing its ts as thread_ts, then acknowledge it with an emoji reaction.

# Reply in thread reply = actions.execute_tool( identifier="user_slack_123", tool_name="slack_send_message", tool_input={ "channel": "C0123456789", "text": "On it — will update the ticket and loop in the on-call team.", "thread_ts": "1700001234.000100" # parent message timestamp } ) # Acknowledge original message with a reaction actions.execute_tool( identifier="user_slack_123", tool_name="slack_add_reaction", tool_input={ "channel": "C0123456789", "name": "eyes", # emoji name, no colons "timestamp": "1700001234.000100" } )
Framework wiring

5 Wiring into your agent framework

Scalekit integrates directly with LangChain. The agent decides what to send or read; Scalekit handles auth on every invocation. No token plumbing in your agent logic.

from langchain_anthropic import ChatAnthropic from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from scalekit.langchain import get_tools slack_tools = get_tools( connection_name="slack", identifier="user_slack_123" ) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a communication assistant. Use the available tools to help manage Slack messages and channels."), MessagesPlaceholder("chat_history", optional=True), ("human", "{input}"), MessagesPlaceholder("agent_scratchpad"), ]) agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), slack_tools, prompt) result = AgentExecutor(agent=agent, tools=slack_tools).invoke({ "input": "Find the last 10 messages in #incidents and summarize any open issues" })
Other frameworks supported
Tool reference

All 18 Slack tools

Grouped by capability. Your agent calls tools by name — no API wrappers to write.

Messaging
slack_send_message
Send a message to a channel or DM. Supports plain text, Block Kit blocks, thread replies, and link unfurling options
slack_update_message
Edit a previously sent message by channel ID and message timestamp
slack_delete_message
Permanently delete a message by channel and timestamp
slack_add_reaction
Add an emoji reaction to any message — specify channel, emoji name (no colons), and message timestamp
slack_pin_message
Pin a message to a channel so it's easily accessible to all members
Conversation History
slack_fetch_conversation_history
Fetch messages from a channel or DM with time-range filtering and cursor-based pagination
slack_get_conversation_replies
Retrieve the full reply thread for a specific parent message by timestamp
Channels
slack_list_channels
List channels the user has access to. Filterable by type: public, private, MPIM, or DM
slack_get_conversation_info
Retrieve metadata for a channel — name, topic, purpose, member count, and settings
slack_create_channel
Create a new public or private channel by name
slack_join_conversation
Join a public channel so the authenticated user becomes a member
slack_leave_conversation
Leave a channel — the user is removed and stops receiving messages
slack_invite_users_to_channel
Invite one or more users to a channel by passing a comma-separated list of user IDs
Users
slack_list_users
List all users in the workspace with profile information and pagination support
slack_get_user_info
Retrieve detailed profile data for a specific user by ID
slack_lookup_user_by_email
Find a user's Slack ID from their email address — requires users:read.email scope
slack_get_user_presence
Check whether a user is currently active or away
slack_set_user_status
Set the authenticated user's status text, emoji, and optional expiration timestamp
Connector notes

Slack-specific behavior

Bot token vs. user token — the distinction matters for your agent
Slack issues two separate tokens per installation: a bot token (xoxb-) acting as your app's identity, and a user token (xoxp-) acting as the specific human who authorized. Tools like slack_send_message will post from different identities depending on which token type is active. If your agent should appear to act on behalf of a specific user — not as a bot — ensure your Scalekit connection is configured with user token scopes (chat:write under User Token Scopes, not Bot Token Scopes) in your Slack app settings.
Scope granularity: private channels and DMs need separate scopes
channels:history and channels:read cover public channels only. Private channels require groups:history and groups:read. Multi-person DMs need mpim:history. Missing the right scope returns a missing_scope error at runtime — not an auth failure — which can be confusing to debug. Ensure the scopes configured in your Scalekit dashboard match the full set of channel types your agent needs to access.
Distribution must be enabled for multi-workspace apps
Slack restricts the standard OAuth install flow to apps that have enabled distribution under Manage Distribution in your app settings. Any app installed by users across multiple workspaces — the typical B2B SaaS case — must have distribution enabled before the Scalekit authorization link will work.
Infrastructure decision

Why not build this yourself

The Slack OAuth flow is documented. Token storage isn't technically hard. But here's what you're actually signing up for:

PROBLEM 01
Bot token vs. user token distinction — different identity, different scopes, different storage, and different handling for every API call where it matters
PROBLEM 02
Token rotation with rotating refresh tokens — new refresh token issued on every exchange, concurrent refresh attempts cause revocation, failures require full re-authorization
PROBLEM 03
Granular scope mismatches that produce missing_scope errors — not auth failures — at runtime, where the wrong scope set silently limits what your agent can see
PROBLEM 04
Revocation detection and multi-tenant token isolation — one user's Slack access must never interfere with another's, even across the same workspace

That's one connector. Your agent product will eventually need Salesforce, Gmail, HubSpot, Notion, and whatever else your customers ask for. Each has its own OAuth quirks and failure modes.

Scalekit maintains every connector. You maintain none of them.

Ready to ship

Connect your agent to Salesforce in minutes

Free to start. No Salesforce Connected App required. Token management fully handled.