Content

How to Connect Slack to an AI Agent (2026)

The hard part is not the API. It is that the token you paste decides whose name the message appears under, and that one token per workspace does not survive your second customer.

IntegrationsOct 27, 2026

Key takeaways

  • -A bot token posts as your app. A user token posts as a person. Choose before you write any code, because switching later changes every message you have sent.
  • -Posting to a channel, posting an ephemeral message and scheduling a message are three separate operations with three different blast radii.
  • -Slack tokens are per workspace, so a product with customers needs one credential per tenant, not one in an environment variable.
  • -Rate limits are per method, not per app, so a chatty agent gets throttled on the one call it makes most.
  • -Allow the specific operations the agent needs and nothing else, then log what it posted.

Getting an AI agent to post in Slack takes ten minutes. Getting it to post as the right identity, in the right workspace, without a stray token in a config file, is the part that takes a week if you get the first decision wrong.

That first decision is the token.

Bot token or user token

Slack gives you two kinds, and they are not interchangeable.

A bot token posts as your app. The message carries your app's name and icon, it works whether or not the person who installed it is still at the company, and it is the right choice for almost every agent.

A user token posts as a person. It acts with that person's permissions and sees what they see. It is the right choice in exactly one situation: when the message genuinely needs to come from a human, usually because a workflow depends on the author.

The reason to decide now rather than later: switching changes the author of every message the agent sends, and anything built on top of those messages — threads, reactions, search, a downstream automation watching for a sender — changes with it. Teams discover this after a few thousand messages.

A user token also inherits a person's entire visibility. Connecting one to an agent means the agent can read every channel that person can read, which in most companies is far more than they think. That is worth a sentence in your security review rather than a shrug, and the full set of questions is in a security checklist before connecting any AI assistant to company data.

"Access to Slack" is not a permission

Here is the thing people skip. Sending a message to a channel is not one operation in Slack's API, it is several, and they are not equivalent.

In our own catalog, three of them come up constantly:

OperationWhat it doesWho sees it
slack_web.chat.postmessage.createSend a message to a channelEveryone in the channel, permanently
slack_web.chat.postephemeral.createSend a private message to one user in a channelOne person, and it disappears
slack_web.chat.schedulemessage.createSchedule a message for laterEveryone, at a time you may not be watching

If your agent only ever needs to confirm something to the person who asked, the ephemeral one is the correct permission and the other two are unnecessary risk. Granting "Slack access" grants all three plus everything else.

This is the argument for an allow list rather than a scope. A scope says the agent may talk to Slack. An allow list says which three calls it may make.

The multi-tenant problem

Slack tokens are per workspace. This is fine while you are the only customer and becomes the central design problem the moment you are not.

If your agent serves ten customers, you need ten tokens, each used only for that customer's requests, and a lookup that cannot possibly pick the wrong one. An environment variable holds exactly one, which is why the environment-variable approach survives the demo and not the second signup. We wrote the architecture for this in multi-tenant OAuth for AI agents.

The failure mode is the specific one worth naming: not an error, but a correct-looking message posted in the wrong company's workspace. Nothing in Slack's API will stop you.

Setting it up

The flow is the same shape for any provider, and there is no .env file in it:

npm install -g swytchcode
swy login
swy init --mode=sandbox

swy get slack
swy discover "send a message to a channel" --project slack

One detail worth knowing about the order: swy discover searches the whole catalog, but swy info only works on a provider you have already fetched. So swy get slack comes first, or info will tell you the operation does not exist when it does.

swy info slack_web.chat.postmessage.create    # check the real parameters
swy add method slack_web.chat.postmessage.create
swy auth connect slack

Only methods added with swy add method can run. If the model decides to delete a channel, the call fails at the runtime before a request reaches Slack. That is not a prompt instruction the model can talk itself out of; it is an absent capability.

The credential goes to ~/.swytchcode/credentials.db, outside your project, injected when a call executes. Nothing secret sits in a file the agent can read, which is the whole point of credential brokering.

Scope the search, or get the wrong provider

A small practical thing that costs people time.

Semantic discovery returns the nearest match in the whole catalog, and the nearest match is not always the provider you meant. Searching for "post a message to a Slack channel" without scoping it can return GitLab's Slack-integration endpoints, because those are also about Slack and also about messages. They are not what you want.

swy discover "send a message to a channel" --project slack

Scoping by project fixes it. Worth doing by reflex, and worth checking the library field in the result before you add anything.

Rate limits bite per method

Slack's rate limits are tiered by method rather than pooled across your app. The consequence for an agent is specific: the call you make most often is the one that throttles, and it throttles while everything else keeps working.

An agent that posts a message per event in a busy channel will hit this. Two things help. Batch where the API allows it, and make the agent's retry behaviour sane — an agent that retries immediately on a 429 makes the problem worse, which is the same class of mistake as retrying a payment.

That second one matters more than it sounds, because a retried Slack post is a duplicate message rather than an error. The reasoning generalises in idempotency explained.

What to log

Slack keeps the messages. It does not keep a record of what your agent tried and was refused, and the refused calls are the ones you will want.

swy audit network   # what ran, with status and timing
swy audit policy    # what was denied or held, and by which rule

For a Slack agent the fields that earn their keep are the operation, the tenant, and the policy decision with the rule version. "Why did the agent post in the wrong channel on Tuesday" is answerable from that and not from a chat transcript. The full list is in logging every API call an agent makes.

When to add an approval step

Posting to a channel is usually low stakes. Two cases are not.

Anything that reaches a lot of people at once, and anything that posts outside the workspace the request came from. For those, a threshold rule is better than hoping:

{
  "id": "broadcast-guard",
  "target": ["slack_web.chat.postmessage.create"],
  "when": { "field": "channel", "operator": "in", "value": ["general", "announcements"] },
  "action": {
    "type": "REQUIRES_APPROVAL",
    "message": "Posting to a company-wide channel needs approval"
  },
  "approval_timeout": "2h"
}

A rule in version control, evaluated when the call runs, rather than a sentence in a prompt that the model may or may not follow. The argument is in a prompt is not a policy and the pattern in policy-as-code for AI agents.

FAQ

How do I connect Slack to an AI agent?

Install a provider's Slack app or create your own, decide between a bot token and a user token, then give the agent only the specific message operations it needs rather than broad Slack access. With the Swytchcode CLI that is swy get slack, swy discover to find the operation, swy info to check its parameters, swy add method to allow exactly that one, and swy auth connect slack to store the credential outside your project.

Should an AI agent use a bot token or a user token?

A bot token in almost every case. It posts as your app, survives the installer leaving the company, and does not inherit one person's entire channel visibility. Use a user token only when the message must genuinely come from a named human, and accept that it grants the agent everything that person can see.

How do I handle multiple Slack workspaces?

One credential per workspace, selected per request, never a single token in an environment variable. The failure this prevents is not an error but a plausible-looking message posted in the wrong customer's workspace, which Slack's API will happily let you do.

Can I stop an AI agent from posting in certain channels?

Yes, but not by telling it. Put a rule in whatever executes the calls, targeting the message operation and matching on the channel, so a post to a company-wide channel is held for approval. A prompt instruction is a suggestion; a rule checked at execution time is not.

Why is my Slack agent getting rate limited?

Slack's limits are tiered per method rather than pooled per app, so the call your agent makes most often throttles while the rest keep working. Batch where the API allows it and make sure a 429 triggers a backoff rather than an immediate retry, since a retried post is a duplicate message rather than a failed one.

Is it safe to connect Slack to an AI assistant?

It depends on what else the assistant can do. Reading Slack is low risk alone, but Slack messages are content other people write, and an assistant reading them while holding any action-capable connector has a path from reading to acting. Narrow the actions first, then worry about the reads.

Setup commands come from the Swytchcode CLI docs, and the operation identifiers above were taken from the live catalog. Slack's own token and rate-limit behaviour is described from Slack's API documentation rather than linked.

More content