Last updated: November 1, 2026

Why the X API refused your DM with a 403

A 403 on an X DM means X refused that message. Most of the time the recipient does not accept messages from you, and retrying will not help. Read the error first: since October 26, 2026 the type field names the refusal, such as dm-sender-not-verified, which tells you whether to skip one person or stop the whole batch.

The difference matters for account safety. A sender problem repeats for every recipient, and a loop of refused cold DMs is the pattern you least want on your account. Check your scopes too: a token without dm.write also fails here.

The three messages you will actually see

These are the only refusal messages in the X DM sends we logged in the 30 days to November 1, 2026. It is a small sample: 468 refusals from outreach campaigns on a small number of accounts, so read the shares as a rough guide.

Message
Share
Whose problem
What to do
You do not have permission to DM one or more participants.
80%
Recipient
Skip this person. Do not retry.
This operation is not permitted.
14%
Unknown (X will not say)
Skip this person. If it hits everyone, check your account.
Your account must be verified to send a Direct Message request.
6%
Sender
Stop the whole batch and fix the account.

On the middle one, X's changelog says: refusals it cannot explain safely stay about:blank with "This operation is not permitted."

The dm-* problem types

X lists these in the create-message schema under https://api.x.com/2/problems/. It publishes the names but no description or status code for each one, so the groups and actions below are our reading of the names, not X's wording.

About
Types
What to do
The recipient
dm-recipient-not-accepting, dm-recipient-cannot-receive, dm-recipient-blocks-sender, dm-recipient-unavailable, dm-recipient-not-found, dm-recipient-restricts-groups, dm-recipient-encrypted-unavailable
Mark the lead unreachable and move on.
Your account
dm-sender-not-verified, dm-sender-phone-required, dm-sender-read-only, dm-sender-not-permitted, dm-sender-dms-closed, dm-sender-declined-dms, dm-sender-blocks-recipient, dm-sender-unavailable, dm-sender-encrypted-unavailable
Pause sending. These tend to repeat for every recipient.
X safety systems
dm-safety-limited, dm-safety-rejected
Stop and review what you are sending. Do not reword and resend in a loop.
Volume or timing
dm-rate-limited, dm-request-in-progress
Wait, then retry with a longer gap.
The message itself
dm-text-empty, dm-text-too-long, dm-duplicate, dm-bad-url, dm-bad-media, dm-bad-attachment, dm-unsupported-message
Fix the payload. Do not send identical text to many people.
The conversation
dm-conversation-not-found, dm-sender-not-in-conversation, dm-participant-not-found, dm-participant-restricted, dm-participant-unavailable, dm-participant-not-added, dm-too-few-participants, dm-too-many-participants
Check the IDs and who is in the conversation.

The list is not complete: dm-not-delivered, dm-not-found, dm-not-owner, dm-inactive-device and a few formatting types also exist, and so does the older recipient-not-messageable.

Handle refusals in code

Branch on the problem type, so one closed inbox does not stop a run, and one sender problem does not burn through a whole list:

send-dm.ts
const res = await fetch(
  `https://api.x.com/2/dm_conversations/with/${recipientId}/messages`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
    body: JSON.stringify({ text }),
  }
);

if (res.status === 201) return "sent";

const problem = await res.json().catch(() => ({}));
const type = (problem.type ?? "").split("/").pop(); // e.g. "dm-sender-not-verified"

if (res.status === 429 || type === "dm-rate-limited") return "wait_and_retry";
if (res.status >= 500) return "retry_with_backoff";
if (type.startsWith("dm-sender-") || type.startsWith("dm-safety-")) return "pause_campaign";
if (res.status === 401) return "refresh_token";
return "skip_recipient";

X says clients that only check the status code are unaffected, so older code keeps working. It just cannot tell a closed inbox from a sender problem. For the full request and OAuth flow, see Send X DMs with the API.

Do not treat every 403 as the recipient's fault

If your code marks all 403s as "unreachable", a sender problem such as a verification requirement quietly marks your entire list as unreachable. Check the type before you update the lead.

Getting fewer refusals

  • Message people who follow you first. Refusals are mostly about non-followers.
  • Check the sending account: verification status, phone number and whether it is limited.
  • Request dm.read, dm.write, tweet.read and users.read with a user token. X's guide rules out app-only auth for DMs.
  • Upload media with the same user, and send it within 24 hours. X names both as causes of failed attachments.
  • Write each message for the person. X's rules prohibit unsolicited automated bulk DMs, and dm-duplicate is its own error.

OpenTweet DM Campaigns covers the sending side: it finds and qualifies leads, drafts a message for each one, and sends only what you approve from your own account, paced and capped. It stops at a refused recipient rather than retrying. It cannot open a closed inbox, and cold outreach still gets refused often. It is on Advanced ($29/mo) and Agency ($49/mo).

7-day free trial with a few limits, or pay now for full limits.

Frequently asked questions

Why does the X API return 403 when I send a DM?

Because X refused that message, which is usually about the recipient and not your code. X's docs name recipient DM settings that block messages from unknown users, or the recipient having blocked you. A 403 can also mean your token lacks a DM scope or your own account cannot send message requests.

What does "You do not have permission to DM one or more participants" mean?

X will not deliver a message from you to that person. The usual cause is that they do not follow you and do not accept messages from people they do not follow. It was the most common refusal in our logs. Retrying the same recipient will not change it until their settings or your relationship change.

What does "This operation is not permitted" mean on a DM?

X refused the message and will not say why. Its October 26, 2026 changelog says refusals it cannot explain safely stay about:blank with this exact text, because a more specific message could reveal a private fact about the recipient. Skip the recipient and move on.

What does "Your account must be verified to send a Direct Message request" mean?

The problem is your account, not the recipient. X requires verification to send that message request. Every other non-follower you try will probably fail the same way, so pause sending and fix the account first. X also lists a dm-sender-not-verified problem type.

What changed in X DM errors on October 26, 2026?

Create-message requests still return HTTP 403 when refused, but the problem type now identifies the refusal, in the form https://api.x.com/2/problems/dm-..., and detail names the cause when that does not reveal something private about the recipient. Code that checks only the status code is unaffected.

Which OAuth scopes do X DMs need?

dm.write, tweet.read and users.read appear on the create-message endpoint. X's integration guide also says dm.read is required with dm.write, so request all four. App-only auth is not supported for DMs, and a Direct Message scope only comes with a user token.

Can any tool get around these refusals?

No. If a recipient's settings refuse your DM, they refuse it through the app, the API and every tool. Be careful with anything that promises otherwise. What a tool can do is check reachability first and stop retrying people who cannot be reached.

Outreach you approve, one message at a time

Find leads, draft personal DMs and send from your own X account, with pacing and daily caps built in.

7-day free trial with a few limits, or pay now for full limits.