Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Slack By James Joyner IV · · 9 min read

Slack API Error Guide: 'not_in_channel' Bot Cannot Post to Channel

Fix the Slack API not_in_channel error: diagnose missing bot membership, private channel invites, conversations.join limits, and wrong channel IDs with curl.

  • #slack
  • #troubleshooting
  • #errors
  • #channels

Overview

The not_in_channel error means your app’s bot user is not a member of the conversation it is trying to act on. Slack requires a bot to be a member of a public or private channel before it can post messages, read history, or pin items there. The API rejects the call rather than silently dropping the message.

You will see this in the JSON body of a chat.postMessage (or conversations.history) response:

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

It occurs whenever a token that is not a member of the target channel calls an endpoint that requires membership — most often the first time an app posts to a brand-new channel, or after someone removes the bot from a channel.

Symptoms

  • chat.postMessage returns ok: false with error: not_in_channel.
  • The bot worked in one channel but fails in another.
  • conversations.history / conversations.replies fail for the same channel.
  • The channel exists and the ID is correct, but the app cannot interact with it.
curl -s -X POST https://slack.com/api/chat.postMessage \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"C0123456789","text":"deploy finished"}'
{
    "ok": false,
    "error": "not_in_channel"
}

Common Root Causes

1. The bot was never invited to the channel

A freshly created channel has no bot members. Posting to it fails until the bot joins or is invited.

curl -s "https://slack.com/api/conversations.info?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": true,
    "channel": {
        "id": "C0123456789",
        "name": "deploys",
        "is_member": false,
        "is_private": false
    }
}

is_member: false confirms the bot is not in the channel.

2. The bot was removed from the channel

If a workspace admin or a /kick removed the bot, previously-working posts begin failing.

curl -s "https://slack.com/api/conversations.members?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" | grep -c "$BOT_USER_ID"
0

A count of 0 means the bot’s user ID is no longer a member.

3. The channel is private and requires an invite

conversations.join only works for public channels. A bot cannot join a private channel on its own — a human member must /invite @yourapp.

curl -s -X POST https://slack.com/api/conversations.join \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"C0123456789"}'
{
    "ok": false,
    "error": "method_not_supported_for_channel_type"
}

For a private channel this means you must invite the bot manually instead.

4. Missing the scope needed to join

conversations.join requires the channels:join scope. Without it the bot cannot self-add to a public channel.

curl -s "https://slack.com/api/auth.test" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
curl -s "https://slack.com/api/apps.permissions.scopes.list" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": false,
    "error": "missing_scope",
    "needed": "channels:join",
    "provided": "chat:write,channels:read"
}

5. Wrong channel ID (a DM, group, or archived channel)

A channel value that points to a DM (D…) or an archived channel can surface as not_in_channel or related errors.

curl -s "https://slack.com/api/conversations.info?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": true,
    "channel": {
        "id": "C0123456789",
        "is_archived": true,
        "is_member": false
    }
}

An archived channel cannot be posted to even after joining — unarchive it first.

6. Using a channel name instead of an ID

Passing a #name rather than the C… ID can resolve to the wrong or no channel, producing membership errors.

curl -s "https://slack.com/api/conversations.list?types=public_channel&limit=200" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" | head -40
{
    "ok": true,
    "channels": [
        {"id": "C0123456789", "name": "deploys", "is_member": false}
    ]
}

Always use the canonical id from conversations.list.

Diagnostic Workflow

Step 1: Confirm the bot identity and token

curl -s "https://slack.com/api/auth.test" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"

Note the user_id (the bot’s ID) and team_id returned — you will match this against the channel members.

Step 2: Inspect the channel membership

curl -s "https://slack.com/api/conversations.info?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"

Check is_member, is_private, and is_archived. is_member: false is the direct confirmation.

Step 3: Join (public) or request an invite (private)

# Public channel, with channels:join scope
curl -s -X POST https://slack.com/api/conversations.join \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"C0123456789"}'

For private channels, have a human run /invite @yourapp inside the channel.

Step 4: Verify the join succeeded

curl -s "https://slack.com/api/conversations.info?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" | grep is_member
"is_member": true,

Step 5: Retry the original call

curl -s -X POST https://slack.com/api/chat.postMessage \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"C0123456789","text":"deploy finished"}'
{
    "ok": true,
    "channel": "C0123456789",
    "ts": "1718900000.000100"
}

Example Root Cause Analysis

A CI pipeline starts posting deploy notifications to a new #releases channel and every run logs not_in_channel. The token works fine for the older #alerts channel, so the credential is valid.

Checking the channel:

curl -s "https://slack.com/api/conversations.info?channel=C09ABCDEF12" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": true,
    "channel": {"id": "C09ABCDEF12", "name": "releases", "is_member": false, "is_private": false}
}

The channel is public but the bot was never added. The app has chat:write but not channels:join, so it cannot self-join either:

{"ok": false, "error": "missing_scope", "needed": "channels:join", "provided": "chat:write,channels:read"}

Fix: add the channels:join scope, reinstall the app, then join the channel:

curl -s -X POST https://slack.com/api/conversations.join \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"C09ABCDEF12"}'

The next pipeline run posts successfully.

Prevention Best Practices

  • On app startup, call conversations.info for every target channel and auto-join (public) or alert (private) when is_member is false.
  • Grant channels:join up front so the bot can self-add to public channels without human intervention.
  • Always store and use the C… channel ID, not the #name, to avoid resolution drift.
  • Subscribe to the member_left_channel event so you are notified the moment the bot is removed, before the next post fails.
  • Treat not_in_channel as retryable: join, then re-issue the post, rather than dropping the notification.
  • For ad-hoc triage, the free incident assistant can classify Slack posting failures into the likely membership or scope cause. See more in Slack guides.

Quick Command Reference

# Who is this token?
curl -s "https://slack.com/api/auth.test" -H "Authorization: Bearer $SLACK_BOT_TOKEN"

# Is the bot a member?
curl -s "https://slack.com/api/conversations.info?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"

# List members of the channel
curl -s "https://slack.com/api/conversations.members?channel=C0123456789" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"

# Join a public channel (needs channels:join)
curl -s -X POST https://slack.com/api/conversations.join \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
  -d '{"channel":"C0123456789"}'

# Retry the post
curl -s -X POST https://slack.com/api/chat.postMessage \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
  -d '{"channel":"C0123456789","text":"hello"}'

Conclusion

not_in_channel always means the same thing: the bot is not a member of the conversation it is acting on. The usual root causes:

  1. The bot was never invited to a newly created channel.
  2. The bot was kicked or removed from the channel.
  3. The channel is private and needs a manual /invite @yourapp.
  4. The app lacks the channels:join scope to self-add.
  5. The channel ID points to an archived channel or a DM.
  6. A #name was passed instead of the canonical C… ID.

Confirm membership with conversations.info, join or request an invite, and retry — the binding between bot and channel is the only thing standing between you and a delivered message.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.