Slack Bot Scopes: The Scope Each API Method Needs, Measured on 44 Methods
We gave a Slack bot one scope, called 44 common Web API methods, and read the needed field of every missing_scope error. Then we added the scopes and called them again. The table shows which scope clears each method.
On this page
A Slack bot token can only call the methods its scopes allow. The fastest way to find the scope a method needs is to call it without the scope and read the needed field of the missing_scope error. On 7 October 2026 we did that for 44 common Web API methods: we made a test app in a free-plan workspace with one bot scope, emoji:read, called every method, and recorded each error. Then we added the scopes the errors asked for, reinstalled, and called every method again. The table below is the result.
Two things in the errors catch people out. Some needed values are old scope names you cannot add to a new app: chat:write:bot and channels:write were both refused by the app manifest with illegal_bot_scopes. And clearing the scope is often not the end: a bot with the right scope still gets not_in_channel until it is a member of the channel.
Scope per method, as Slack reported it
"Needed" is copied from the missing_scope error of the one-scope bot. "Scope we added" is the bot scope that made the scope error go away. "Second call" is what the same call returned after the reinstall; anything other than missing_scope means the scope was enough.
| Method | needed in the error | Scope we added | Second call |
|---|---|---|---|
chat.postMessage | chat:write:bot | chat:write | not_in_channel |
chat.update | chat:write:bot | chat:write | message_not_found |
chat.delete | chat:write:bot | chat:write | message_not_found |
chat.postEphemeral | chat:write:bot | chat:write | not_in_channel |
chat.scheduleMessage | chat:write:bot | chat:write | not_in_channel |
conversations.history | channels:history, groups:history, mpim:history, im:history | channels:history | not_in_channel |
conversations.replies | channels:history, groups:history, mpim:history, im:history | channels:history | not_in_channel |
conversations.list | channels:read, groups:read, mpim:read, im:read | channels:read | ok |
conversations.info | channels:read, groups:read, mpim:read, im:read | channels:read | ok |
conversations.members | channels:read, groups:read, mpim:read, im:read | channels:read | ok |
conversations.create | channels:write, groups:write, mpim:write, im:write | channels:manage | ok |
conversations.join | channels:join | channels:join | ok |
conversations.invite | channels:write.invites, groups:write.invites | channels:write.invites | already_in_channel |
conversations.open | channels:write, groups:write, mpim:write, im:write | im:write | ok |
conversations.setTopic | channels:write.topic, groups:write.topic, mpim:write.topic, im:write.topic | channels:write.topic | ok |
conversations.mark | channels:write, groups:write, mpim:write, im:write | channels:manage / im:write | ok |
users.list | users:read | users:read | ok |
users.info | users:read | users:read | ok |
users.lookupByEmail | users:read.email | users:read.email | ok |
users.profile.get | users.profile:read | users.profile:read | ok |
users.getPresence | users:read | users:read | ok |
users.conversations | channels:read, groups:read, mpim:read, im:read | channels:read | ok |
reactions.add | reactions:write | reactions:write | message_not_found |
reactions.get | reactions:read | reactions:read | message_not_found |
pins.add | pins:write | pins:write | message_not_found |
pins.list | pins:read | pins:read | ok |
files.list | files:read | files:read | ok |
files.info | files:read | files:read | file_not_found |
files.getUploadURLExternal | files:write | files:write | ok |
team.info | team:read | team:read | ok |
usergroups.list | usergroups:read | usergroups:read | ok |
bookmarks.list | bookmarks:read | bookmarks:read | ok |
dnd.info | dnd:read | dnd:read | ok |
canvases.create | canvases:write | canvases:write | free_teams_cannot_create_standalone_canvases |
team.profile.get | users.profile:read | users.profile:read | ok |
bots.info | users:read | users:read | ok |
When needed lists several scopes, any one of them is enough for that kind of conversation: channels: for public channels, groups: for private channels, mpim: for group DMs and im: for DMs. We added only the public-channel scope each time, and the calls on our public test channel passed.
Methods that need no scope, or no bot token
| Method | Result with a one-scope bot | What it means |
|---|---|---|
auth.test | ok | No scope needed |
chat.getPermalink | message_not_found | Passed the scope check with no extra scope |
views.publish | not_enabled | No scope, but the App Home tab must be turned on in the app settings |
reminders.add | not_allowed_token_type | Bot tokens cannot use it; it needs a user token |
search.messages | not_allowed_token_type | User token only |
apps.connections.open | not_allowed_token_type | Needs an app-level xapp- token with connections:write |
canvases.create | free_teams_cannot_create_standalone_canvases (after adding canvases:write) | Scope fine, plan too low |
Legacy names in the needed field
Three errors named a scope that a new Slack app cannot request:
- •
chat.postMessage,chat.update,chat.delete,chat.postEphemeralandchat.scheduleMessageall saidneeded: chat:write:bot. Addingchat:writecleared all five. - •
conversations.create,conversations.openandconversations.marksaidneeded: channels:write, groups:write, mpim:write, im:write. For a bot,channels:manageclearedcreate, andim:writeclearedopen.
We tried putting chat:write:bot and channels:write in a manifest, and apps.manifest.validate answered illegal_bot_scopes. If you search for a needed value and find nothing in the app settings, look for the current name: chat:write for posting, channels:manage for creating and archiving channels, im:write for opening DMs. Our page on the missing_scope error covers reading the error itself.
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
Scope is not membership
After the reinstall, five methods still failed with not_in_channel: the four chat.* posting calls and conversations.history/replies. The bot had the scope but was not in the channel. Once conversations.join (scope channels:join) had run, chat.postMessage returned ok and conversations.history returned the messages. A post to a different public channel the bot had not joined still failed with not_in_channel; chat:write.public is the scope that lets a bot post to public channels without joining. More on that error in Slack not_in_channel.
A starting set for a common bot
From the table, a bot that posts in public channels it has joined, reads their history, reacts and DMs people needs these scopes:
| The bot should | Scopes |
|---|---|
| Post and edit its own messages | chat:write |
| Post in public channels without being invited | chat:write.public |
| Read messages in public channels it is in | channels:history |
| Find channels and their members | channels:read |
| Join public channels by itself | channels:join |
| Look up people | users:read, plus users:read.email for users.lookupByEmail |
| Open a DM with a person | im:write |
| React to messages | reactions:write |
| Upload files | files:write |
Add the groups:, mpim: and im: variants only if the bot must work in private channels, group DMs or DMs. Every scope you add is listed on the install screen the person installing the app approves.
How we ran the scan
This is the loop from the script we ran for both passes. It sends each call with the bot token and saves the error fields; the method list is in the tables above.
import json, time, requests
def scan(token, methods):
out = []
for name, args in methods:
r = requests.post(
"https://slack.com/api/" + name,
headers={"Authorization": "Bearer " + token},
data=args, timeout=30,
).json()
out.append({"method": name, "ok": r.get("ok"), "error": r.get("error"),
"needed": r.get("needed"), "provided": r.get("provided")})
time.sleep(1.1) # stay under the per-method rate limits
return out
First-pass output for one method, as the script saved it:
{"method": "chat.postMessage", "ok": false, "error": "missing_scope",
"needed": "chat:write:bot", "provided": "emoji:read"}
FAQ
Do I need to reinstall the app after adding a scope?
Yes. We added groups:read to the manifest with apps.manifest.update and called conversations.list for private channels: still missing_scope, with needed: groups:read. After a reinstall it returned ok. The bot token string stayed the same through both reinstalls, so nothing that stores the token has to change.
Why does files.info say file_not_found with files:read?
Our bot asked about a file that a different test app had uploaded and never shared in a channel. With files:read the scope check passed, but the bot could not see that file.
Stop Jiggling Your Mouse.
Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.
Related Articles
Slack File Size Limit: 1 GB Is Exactly 1,073,741,824 Bytes, Tested
We uploaded files of 50 MB, exactly 1 GiB and 1 GiB plus one byte to Slack through its upload API. The limit is 1,073,741,824 bytes, and a failed upload can still leave a broken file behind.
Slack Dark Mode: Where the Setting Is, and Which Custom Emoji Disappear
Slack's dark mode is under Preferences > Appearance > Color Mode, with Light, Dark and System. We switched modes in Slack web with three test emoji and measured which ones vanish.
Slack Activity Tab: What Lands There, the Filters, and Why You Cannot Hide It
The Activity tab collects mentions, reactions, thread replies, reminders and DMs in one feed. We sent a mention and a /remind to a test account and opened every Activity setting in Slack web.