Back to Blog
Guide

Slack not_in_channel Error: Join, Invite, or chat:write.public

not_in_channel means the bot is not a member of the channel you posted or read from. Public channel: conversations.join or chat:write.public. Private channel: /invite the app. Every response here came from a real app.

Slack Green Team
September 29, 2026
September 29, 2026
3 min read
Share:
slack api
developers
slack errors

Slack returns not_in_channel when your bot tries to post to or read from a channel it is not a member of. For a public channel there are two fixes: have the bot join with conversations.join (scope channels:join), or add the chat:write.public scope so it can post without joining. For a private channel, a person in the channel has to add the app, for example with /invite @YourApp. A private channel the bot is not in usually does not return not_in_channel at all; it returns channel_not_found. We reproduced every case below on 29 September 2026 with a test bot in a one-person workspace.

What we sent and what Slack answered

The bot had only chat:write. We created a public channel, a private channel and an archived channel, and did not add the bot to any of them.

CallChannelResponse
chat.postMessagepublic, bot not a membernot_in_channel
chat.postMessagepublic, by name #w1-lab-publicnot_in_channel
chat.postMessagepublic, by name without #not_in_channel
chat.postMessageprivate, bot not a memberchannel_not_found
chat.postMessagearchived, bot not a membernot_in_channel
chat.postMessagearchived, bot is a memberis_archived
conversations.historypublic, bot not a membernot_in_channel
reactions.addpublic, bot not a membernot_in_channel

Each came back as HTTP 200 with a body like this:

{"ok": false, "error": "not_in_channel"}

Two rows surprise people. A channel name works as well as an ID for public channels: Slack found the channel and then refused because the bot was not in it. And the archived channel returns not_in_channel until the bot is a member; only then do you see the real reason, is_archived.

Fix 1: join the public channel with conversations.join

Add the channels:join bot scope, reinstall the app, then call:

curl -s https://slack.com/api/conversations.join \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -d channel=C0C4RQXFPAT

Our bot got {"ok": true, ...} with the channel object, a "joined #w1-lab-public" line appeared in the channel, and the next chat.postMessage returned ok: true. Joining also fixed conversations.history and reactions.add, which the next fix does not.

conversations.join works only on public channels. On our private channel it returned channel_not_found, and on the archived one is_archived.

Fix 2: post without joining, with chat:write.public

Add the chat:write.public scope and reinstall. The bot can then post to any public channel without being a member. Our first post after the reinstall went through, then the bot joined, then it posted again:

The bot posted with chat:write.public while not a member, then joined the channel and posted again

This scope covers posting only. In the same state, conversations.history and reactions.add on that channel still returned not_in_channel. It also does nothing for private channels: chat.postMessage to our private channel still returned channel_not_found.

Use it when a bot posts alerts to many public channels and never needs to read them. Otherwise, join.

Never appear "away" on Slack again

Cloud-based. No downloads. Works 24/7 even when your laptop is off.

Fix 3: invite the app to a private channel

A bot cannot add itself to a private channel. Someone in the channel types /invite @ and the app's name. Slack's picker marks the app Not in channel:

Typing /invite @API Error in a private channel: the picker shows the app as Not in channel

After the invite, the post that had failed with channel_not_found returned ok: true, and conversations.history worked too. When we removed the bot with conversations.kick, the next post failed with channel_not_found again.

If a person @-mentions the app in a private channel it is not in, Slack offers the same fix to that person only:

Slack's only-visible-to-you prompt: You mentioned @API Error Lab, but it's not in this private channel, with Add Them

The message with the mention is still posted; the prompt is only visible to the sender.

If you still get not_in_channel

  • • Check the token. If your code has two tokens, the call may use the one whose bot was never invited. auth.test returns the user_id of the bot the token belongs to; that user must be in the channel's member list.
  • • Check the channel. A webhook URL is tied to the channel picked at install, but chat.postMessage uses whatever channel you pass. Copy the ID from the channel's details, at the bottom of the About tab.
  • • For GitHub Actions, the invite step is the one people skip. The workflow setup is in how to send a Slack message from GitHub Actions.

The related errors each have their own page: missing_scope when the token lacks a permission, and channel_not_found when Slack cannot see the channel at all.

FAQ

Why do I get channel_not_found instead of not_in_channel on a private channel? Slack hides private channels from apps that are not members, so to the bot the channel does not exist. Invite the app and the error goes away.

Does chat:write.public need a reinstall? Yes, like any scope change. Until the app is reinstalled, the token keeps its old scopes.

Can an incoming webhook hit not_in_channel? We never saw it from one. A webhook posts only to the channel chosen when it was created, and none of our 450 test posts to a webhook returned this error. Its errors are covered in Slack webhook curl example.

Always Active

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

Guide

Slack Member ID: Where to Copy It, and What the U Means

A Slack member ID is the U... code that identifies a person. Copy it from their profile under the three-dot menu, or look it up with users.info and users.list. Tested in two workspaces, with the ID formats we found.

Slack Green Team
Guide

Slack API Rate Limits: What We Hit, and the 429 You Get Back

Slack answers too many calls with HTTP 429 and a Retry-After header. We burst chat.postMessage, conversations.history and an incoming webhook from an internal app and logged every response: where the 429s started, the bodies, and a retry loop that never hit one.

Slack Green Team
Guide

Slack files.upload Deprecated: The 3-Step Upload That Replaces It

files.upload now returns method_deprecated. Use files.getUploadURLExternal, POST the bytes, then files.completeUploadExternal, or files_upload_v2 in the SDK. Tested with curl and Python, including two silent failures.

Slack Green Team