Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Slack By James Joyner IV · · 8 min read Last reviewed Jul 2026

Slack Error Guide: 'cannot_dm_bot' — You Can't Open a DM With Another App

Quick answer

Fix the Slack API cannot_dm_bot error: conversations.open was pointed at a bot user. Diagnose bot vs human IDs, message the human instead, and DM users from your app with curl.

  • #slack
  • #api
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Slack error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

cannot_dm_bot means your app tried to open a direct message with another bot user, and Slack refuses. Bots cannot hold a one-to-one DM conversation with other bots. This almost always happens when code that resolves a “user to notify” accidentally resolves a bot’s user ID — for example a channel’s app integration, a webhook’s bot identity, or an on-call mapping that points at a bot instead of a person — and then feeds that ID to conversations.open.

The response body:

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

It occurs on conversations.open (and the DM path of chat.postMessage when the target resolves to a bot). The fix is data, not permissions: point the DM at a human user ID (U… belonging to a person), or notify the bot’s owning humans in a channel instead.

Symptoms

  • conversations.open fails for one specific “user” while working for everyone else.
  • The failing target ID belongs to an app/integration, not a person.
  • A notification meant for a team lands nowhere because the resolved recipient was a bot.
  • users.info on the target shows is_bot: true.
curl -s -X POST "https://slack.com/api/conversations.open" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"users":"U0BOTABC01"}'
{
    "ok": false,
    "error": "cannot_dm_bot"
}

Common Root Causes

1. The recipient ID actually belongs to a bot

The classic case: an ID that looks like a normal U… user is a bot user. Bots have U… IDs too, so you cannot tell by shape alone — only users.info reveals is_bot: true.

curl -s "https://slack.com/api/users.info?user=U0BOTABC01" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": true,
    "user": {
        "id": "U0BOTABC01",
        "name": "deploybot",
        "is_bot": true
    }
}

2. Using a bot_id where a user_id was needed

Event and message payloads carry both bot_id (B…) and, for bots, a user (U…). Mixing these up — or resolving the app that posted a message as the “author to DM” — targets a bot.

3. On-call / owner mapping points at an integration

An escalation table maps a service to “the account that owns its alerts,” and that account is a bot/service user rather than a human on-call engineer. Every page to that service hits cannot_dm_bot.

4. Echoing a message back to its author, which is a bot

A bot that DMs whoever triggered an event will try to DM another bot when the trigger came from an automated integration, not a person.

5. Notifying the app itself

Code that resolves “who to notify” to the running app’s own user ID (e.g., from auth.test) then tries to DM it — an app cannot open a DM with itself either.

Diagnostic Workflow

Step 1: Reproduce and read the exact error

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

Step 2: Confirm the target is a bot

curl -s "https://slack.com/api/users.info?user=U0BOTABC01" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": true,
    "user": {
        "id": "U0BOTABC01",
        "name": "deploybot",
        "is_bot": true,
        "profile": { "real_name": "Deploy Bot" }
    }
}

is_bot: true confirms the recipient is an app — that is why the DM is refused.

Step 3: Find the human you actually meant to reach

Resolve the intended person by email or roster lookup and confirm they are NOT a bot:

curl -s "https://slack.com/api/users.lookupByEmail?email=oncall@acme.com" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{
    "ok": true,
    "user": {
        "id": "U0HUMAN42",
        "is_bot": false,
        "profile": { "email": "oncall@acme.com" }
    }
}

Step 4: Open the DM with the human ID

curl -s -X POST "https://slack.com/api/conversations.open" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"users":"U0HUMAN42"}'
{
    "ok": true,
    "channel": { "id": "D0HUMAN42DM" }
}

Step 5: Post to the returned DM channel

curl -s -X POST "https://slack.com/api/chat.postMessage" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"D0HUMAN42DM","text":"You are now on call for payments."}'
{"ok": true, "channel": "D0HUMAN42DM", "ts": "1720440200.000300"}

Example Root Cause Analysis

An on-call notifier DMs the engineer responsible for each service. For the payments service it fails every time with cannot_dm_bot, while every other service notifies fine.

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

Looking up the ID stored for payments shows the truth:

curl -s "https://slack.com/api/users.info?user=U0BOTABC01" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"
{"ok": true, "user": {"id": "U0BOTABC01", "name": "deploybot", "is_bot": true}}

The escalation table had captured the user ID from a message posted by deploybot — the automation that owns payments deploys — instead of the human owner. Because that ID is a bot, the DM is impossible.

Fix: repopulate the mapping from the human on-call roster (resolving by email), and validate is_bot: false before storing any notification target. After the correction, the DM opens against U0HUMAN42 and the page lands.

Prevention Best Practices

  • Validate is_bot: false (via users.info) before storing or DMing any “user to notify”; bot IDs look identical to human IDs.
  • Resolve notification targets from an authoritative human roster (e.g. users.lookupByEmail) rather than from message authorship, which can be a bot.
  • Keep bot_id (B…) and user (U…) distinct in your data model; never feed a bot_id to conversations.open.
  • For alerts that “belong to” an automated service, notify a human owner or a channel, not the integration’s bot identity.
  • Handle cannot_dm_bot gracefully: fall back to posting in the service’s owning channel and flag the misconfigured mapping instead of silently dropping the notification.
  • Never try to DM your own app’s user ID; if a target resolves to the running bot, skip it.

Quick Command Reference

# Reproduce
curl -s -X POST "https://slack.com/api/conversations.open" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
  -d '{"users":"U0BOTABC01"}'

# Is the target a bot?
curl -s "https://slack.com/api/users.info?user=U0BOTABC01" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"

# Resolve the real human by email
curl -s "https://slack.com/api/users.lookupByEmail?email=oncall@acme.com" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN"

# Open the DM with the human, then post
curl -s -X POST "https://slack.com/api/conversations.open" \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
  -d '{"users":"U0HUMAN42"}'

Conclusion

cannot_dm_bot is unambiguous: you pointed conversations.open at another bot, and bots cannot DM each other. The usual root causes:

  1. The recipient ID actually belongs to a bot (is_bot: true).
  2. A bot_id was used where a human user_id was needed.
  3. An on-call/owner mapping points at an integration, not a person.
  4. The app echoed a message back to a bot author.
  5. The app tried to DM its own user ID.

Confirm the target with users.info, resolve the human you actually meant (by email or roster), open the DM against that U… ID, and post. Add an is_bot guard so a bot ID can never enter your notification targets again. For fast triage of misrouted notifications, the incident assistant can spot a bot-vs-human mismatch. See more in Slack guides.

Free download · 368-page PDF

Fixed it? Get 500 Slack & DevOps AI prompts — free

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.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

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.