Twitter (X) Welcome Message: Is the API Still Alive, and How to Send One in 2026

If you searched for "twitter welcome message" you probably want one of three things: to know whether the welcome-message feature X launched for businesses in 2016 still exists, to turn off a greeting an old app set up and that still fires on your account, or to build a bot that greets people who just followed you. The search results are forum threads asking exactly those questions and listicles for auto-DM tools, and none of them answers the first question plainly. This page does, with the endpoint names and the pages on docs.x.com we checked.
The short version: the API that made welcome messages possible, the v1.1 direct_messages/welcome_messages family, is retired for every self-serve tier, and X API v2 does not have a replacement. What v2 has is plain Direct Message endpoints: send a message to a user, send into a conversation, list recent events, delete one. A welcome message in 2026 is therefore a flow you run yourself: notice the follow, decide whether the account can receive a DM, send one message. We walk through both ways to send it, the official v2 endpoint under OAuth 2.0 and a logged-in session on twitterapi.io, with request shapes, prices and the rules you should keep to.
And because "send a DM to every new follower" sounds simpler than it is, we measured the part nobody documents: who those new followers are. We pulled the newest 200 followers of 8 organization accounts (1,600 records, aggregated, never identified) and looked the 200 newest up by id. The follower list said none of them could be messaged; the profiles said half could. A third were accounts created in the last 30 days, a third had never posted. Every design choice below, from the eligibility check to the wording advice, comes from one of those numbers.
What happened to the Twitter welcome message API, and what X's own settings still offer
Welcome messages were a 2016 addition to Twitter's Direct Message platform for businesses: a greeting shown when someone opened a DM conversation with the account, optionally with quick-reply buttons, created and managed through the v1.1 endpoints POST direct_messages/welcome_messages/new, GET .../list, GET .../show, DELETE .../destroy and a welcome_messages/rules family that set which one was the default. Third-party tools built auto-DM products on top of them.
Where they stand now. We looked for a status page on docs.x.com this year and found none; the old reference URLs on developer.twitter.com redirect to the docs home page. The v1.1-to-v2 migration guide for Direct Messages (docs.x.com/x-api/direct-messages/manage/migrate) lists exactly two v1.1 endpoints, POST direct_messages/events/new and DELETE direct_messages/events/destroy, and maps them to v2. Welcome messages do not appear, which is the documentation's way of saying there is nothing to migrate to. The X API changelog records DM endpoint additions (six v2 DM endpoints in October 2022, DM blocking in September 2024, a dm.received event stream in 2026) and no welcome-message entry.
What the developer forum says. The threads that rank for this query are people who cannot switch off a greeting set years ago: "X DM Welcome Message" (devcommunity.x.com/t/x-dm-welcome-message/214508), "Legacy welcome message cannot be deleted, error 453 on v1.1 endpoints" (.../t/261820), "Basic tier, getting error 403 on DM welcome message deployment" (.../t/236675) and several more. Their common answer, from X staff, is that the v1.1 welcome-message endpoints are sunset, that v2 does not yet support welcome messages, and that the create, update and delete API is v1 and is disabled for the Free, Basic and Pro plans; staff have granted temporary access to GET /1.1/direct_messages/welcome_messages/list.json and DELETE .../destroy.json?id=<id> (OAuth 1.0a) so people can delete what they have. Error 453, the code these developers hit, is the one X returns when an app's access level does not include the endpoint.
What about the X Chat API? X has a newer docs.x.com/xchat section for encrypted chat, including bot accounts tied to a project (docs.x.com/xchat/bots). Its bot pages cover creating bots, rotating tokens and updating identity; they say nothing about an automatic first message or greeting. So as of this year, no X API product sends a message for you when someone opens a conversation or follows you. If you want that behavior, your code sends it.
The consumer side: what X's own settings offer. People also search this phrase expecting a toggle in the X app. There is no per-account welcome-message setting in X's consumer product; the feature was always configured through a developer app or a third-party tool that used those v1.1 endpoints. What the X app does control is who may message you: X's help page on Direct Messages (help.x.com/en/using-x/direct-messages) documents the message-request settings (allow requests from everyone, from verified users, or from no one) and how to manage message requests. Those settings are what the canDm field in the data below reflects from the other side.
If an old greeting still fires on your account. You cannot delete it from the app. The paths that have worked for people on the developer forum are to regain access to the app that created it and call DELETE /1.1/direct_messages/welcome_messages/destroy.json?id=<id> after listing with .../list.json, or, if the app is gone or your tier returns error 453, to open a thread on devcommunity.x.com asking for temporary access or a server-side removal, which staff have done case by case. Revoking the old app's access under Settings, Security and account access, Apps and sessions is also worth doing; several threads report that the greeting persisted until the app was removed.
If you run a business account and want a greeting. Your remaining options are: a pinned post or bio line that tells people what to DM you about; the Account Activity API plus your own code that replies when a dm.received event arrives (an auto-reply to the first inbound message, which is a different page, see our Twitter auto-reply guide); or the welcome-on-follow flow built below, which messages people who just followed you rather than people who open a conversation.
What X API v2 offers for Direct Messages instead
The v2 DM surface is small and documented in two sections, Manage (docs.x.com/x-api/direct-messages/manage/introduction) and Lookup (.../lookup/introduction). Everything needs a user access token obtained through OAuth 2.0 Authorization Code with PKCE (OAuth 1.0a user context is still accepted); app-only bearer tokens cannot touch DMs.
The rate limits are from the table at docs.x.com/x-api/fundamentals/rate-limits, which also shows the same 1,440-per-24-hours ceiling at app level for each send endpoint. We did not find the 500-DMs-a-day figure that circulates in auto-DM tool pages anywhere on docs.x.com, so we do not repeat it; the documented number is 1,440. Prices are from docs.x.com/x-api/getting-started/pricing: "DM Interaction: Create" at $0.015 per request, "DM Event: Read" at $0.010 per resource, and the webhook events dm.received at $0.010 (dm.sent is not billed).
Scopes and body. The reference page for the one-to-one send (docs.x.com/x-api/direct-messages/create-dm-message-by-participant-id) lists dm.write, tweet.read and users.read; the integration guide adds that dm.read is required alongside dm.write. The JSON body is {"text": "..."} with an optional attachments: [{"media_id": "..."}], at least one of the two required, and the response carries dm_conversation_id and dm_event_id. The error schema includes a RecipientNotMessageableProblem, which is what you get when the recipient does not accept messages from you, and it is the official equivalent of the eligibility check we build below.
No quick replies, no triggers. There is no v2 field for quick-reply buttons, no welcome_message_id, and no event that fires when a user opens your DM window. The one thing that comes close is the Account Activity API's follow_events, covered in the next section, which tells your webhook that someone followed you so your code can react.
Step 1: detect new followers (poll and diff, or the official follow webhook)
A welcome DM needs a trigger, and the trigger is the follow. There are two ways to see it.
Poll the follower list, newest first. GET /twitter/user/followers?userName=<you> on twitterapi.io returns followers in reverse chronological order of follow, about 200 per page, as snake_case records with id, screen_name, name, description, location, created_at, followers_count, statuses_count, protected, can_dm, plus has_next_page and next_cursor. No X account or OAuth is needed for the read. Fetch the first page, keep a set of ids you have already seen (a JSON file or one table), and the difference is your new followers. Because the list is newest first, one page is enough until you gain more than 200 followers between polls; if the whole page is new, follow next_cursor until you meet a known id. Pricing is per follower returned, 1 credit each when the page has 200, 2 when 100 to 199, 3 when 20 to 99 (pageSize default 200, minimum 20), with a 60-credit minimum per request: a full page is $0.002, and polling every 10 minutes is 144 pages or about $0.29 a day. You pay only for the data returned.
Or subscribe to X's follow events. X's Account Activity API (docs.x.com/x-api/account-activity/introduction) delivers follow_events and unfollow_events to a webhook you register through the v2 Webhooks API and then subscribe your user to (POST /2/account_activity/webhooks/:webhook_id/subscriptions/all). The pay-per-usage tier allows 3 subscriptions and 1 webhook; each follow.follow event is billed at $0.010. This is the right choice if you already run an OAuth 2.0 app and want second-level latency; the poll is the right choice if you want no X developer app at all, or you are greeting followers of several accounts.
The function below does the poll-and-diff. It stops paging as soon as it sees a known id, so on a quiet account it costs one request per run.
import json, os, requests
BASE = "https://api.twitterapi.io"
H = {"X-API-Key": os.environ["TWITTERAPI_IO_API_KEY"]}
SEEN_FILE = "welcomed-or-seen.json" # ids we have already seen (greeted or skipped)
def load_seen():
return set(json.load(open(SEEN_FILE))) if os.path.exists(SEEN_FILE) else set()
def new_followers(user_name, seen, max_pages=3):
"""Newest followers first; stop at the first id we already know. Returns snake_case follower records."""
fresh, cursor = [], ""
for _ in range(max_pages):
r = requests.get(f"{BASE}/twitter/user/followers", headers=H,
params={"userName": user_name, "cursor": cursor, "pageSize": 200}, timeout=30)
r.raise_for_status()
body = r.json()
if body.get("status") == "error":
raise RuntimeError(body.get("msg"))
page = body.get("followers") or []
for f in page:
if f["id"] in seen:
return fresh # everything older than this is known
fresh.append(f)
if not body.get("has_next_page") or not body.get("next_cursor"):
break
cursor = body["next_cursor"]
return fresh
if __name__ == "__main__":
seen = load_seen()
first_run = not seen
fresh = new_followers("your_handle", seen)
if first_run:
# seed the set without greeting anyone: a welcome message is for people who follow from now on
json.dump([f["id"] for f in fresh], open(SEEN_FILE, "w"))
else:
print(len(fresh), "new follower(s)")Step 2: who can actually receive the DM (do not trust the list's can_dm)
Two things decide whether your welcome DM can be delivered: the account must accept message requests from accounts it does not follow (you are not mutuals yet), and it should not be a protected account, which you cannot usefully message before it approves you. Both look like fields you already have in the follower record, can_dm and protected. One of them is a trap.
The follower list's can_dm was false for every one of 1,600 followers. Across the newest 200 followers of @github, @vercel, @Docker, @NASA, @nvidia, @Shopify, @Cloudflare and @Stripe, not a single record had can_dm: true (and verified was false for all 1,600 too). That cannot be right for a population that size, so we looked the 25 newest followers of each account up again with GET /twitter/user/batch_info_by_ids?userIds=<csv>, 200 profiles in 2 requests. That endpoint returns camelCase profiles (userName, canDm, protected, isAutomated, followers, statusesCount, createdAt), and there canDm was true for 98 of 200, 49.0%. The list field is a placeholder; the profile field carries the real value. Budget 18 credits per profile ($0.00018) for the lookup and treat the follower list as a source of ids.
Of the 200 profiles, 4.5% were protected and 48.0% (96) were both open to DMs and not protected: that is the share of new followers a welcome DM can reach on these accounts. The spread between accounts is wide, and it tracks account age: canDm was true for 87.1% of the 62 looked-up accounts created in the last 30 days, against 13.0% of the 23 aged 30 days to a year, 35.0% of 40 aged 1 to 3 years, 20.6% of 34 aged 3 to 8 years and 48.8% of 41 older than 8 years. Brand-new accounts tend to have message requests open; accounts that have been around a while have often closed them. @NASA's 92% follows from its follower mix, below. None of the 200 profiles carried isAutomated: true.
What canDm means, and what it does not. X's documentation does not define the field. In our reading it reflects whether the profile accepts message requests from accounts it does not follow, which is exactly the condition a first DM from you needs; treat it as a filter that saves you failed sends, not as a guarantee. The send call's own response (RecipientNotMessageableProblem on the official API, a status: "error" with a message on send_dm_to_user) is the final word, and a failed send should put the id in your seen set, not in a retry queue.

Step 3: send the welcome DM, two ways
Once you have an eligible new follower, you send exactly one message. There are two routes, and they differ in who is doing the sending.
Route A: the official X API v2. Your app holds a user access token for your own account, obtained through OAuth 2.0 PKCE with the scopes dm.write dm.read tweet.read users.read (plus offline.access if you want a refresh token). You call POST https://api.x.com/2/dm_conversations/with/{participant_id}/messages with Authorization: Bearer <user token> and the body {"text": "..."}. The response is {"data": {"dm_conversation_id": "...", "dm_event_id": "..."}}. Keep under 15 sends per 15 minutes and 1,440 per 24 hours per user; the price is $0.015 per DM on the pay-per-usage rate card, and you need the developer account and the app. Fully within X's terms as long as your content follows the automation rules, and the recipient can report or block like any DM.
Route B: a logged-in session on twitterapi.io. Here the DM goes out as your account, through the same interface a browser session uses, and no X developer app is involved. One call to POST /twitter/user_login_v2 with user_name, email, password, a proxy you supply (the documentation asks for a high-quality residential proxy) and optionally totp_secret (the base32 2FA seed, not a 6-digit code; strongly recommended) costs $0.10 and returns login_cookies. Then each POST /twitter/send_dm_to_user with login_cookies, user_id, text, proxy and optionally media_id (from /twitter/upload_media_v2) or reply_to_message_id costs $0.002 and returns message_id, status and message. The endpoint documentation says plainly: "You can only send DMs to those who have enabled DMs. Sometimes it may fail, so be prepared to retry."
Rules for Route B, in plain words. The call acts as that account, so X can rate-limit, lock or suspend it if the pattern looks like spam; keep a human pace (one message every minute or two, a daily cap you would be comfortable sending by hand); the session can expire and need another $0.10 login; every call that returns HTTP 200 is billed, including ones whose body says status: "error", so check status and never loop on failures. And the framing matters: this is a greeting to someone who just chose to follow you, one message, with a clear reason and no link wall. It is not a tool for messaging people who did not follow you, and we do not describe it as one.
What to write. The data below says a third of the accounts that follow brand accounts are under 30 days old and a third have never posted. Write for a newcomer: who you are in one line, what you post, one useful link at most, and an easy out ("reply stop and you will not hear from this bot again", and honor it by adding the id to the seen set). Do not ask for anything. X's automation rules (help.x.com/en/rules-and-policies/x-automation) are the document to read before you ship either route; they are the standard your account will be judged against.
The snippet below is Route A in Python; the full script at the end of the page is Route B wired to Steps 1 and 2.
import os, requests
# Route A: official X API v2. USER_TOKEN is an OAuth 2.0 user access token for YOUR account
# obtained with the scopes: dm.write dm.read tweet.read users.read (offline.access for refresh).
USER_TOKEN = os.environ["X_USER_ACCESS_TOKEN"]
def send_welcome_official(participant_id: str, text: str) -> dict:
r = requests.post(
f"https://api.x.com/2/dm_conversations/with/{participant_id}/messages",
headers={"Authorization": f"Bearer {USER_TOKEN}", "Content-Type": "application/json"},
json={"text": text}, # optional: "attachments": [{"media_id": "..."}]
timeout=30,
)
if r.status_code == 429:
raise RuntimeError("rate limited: 15 per 15 min / 1,440 per 24 h per user")
body = r.json()
if "errors" in body:
# e.g. RecipientNotMessageableProblem: the person does not accept DMs from you. Do not retry.
raise RuntimeError(body["errors"][0].get("title"))
return body["data"] # {"dm_conversation_id": "...", "dm_event_id": "..."}
print(send_welcome_official("2244994945", "Thanks for the follow! I post about X API tooling; reply 'stop' and this bot will never message you again."))Who follows a brand account: 1,600 newest followers in numbers
A welcome message is written for an audience, and the audience that follows organization accounts looked different from what we expected. Across the newest 200 followers of the 8 accounts (collected in 2026):
A third are brand-new. 31.0% (496 of 1,600) were created within 30 days of collection. 14.3% were 30 days to a year old, 14.9% one to three years, 20.1% three to eight years, and 19.7% older than eight years. The median account was 510 days old, but that median hides a two-humped shape: a large group of fresh accounts and a large group of long-time users.
A third have never posted, a third have no followers. 34.7% had statuses_count: 0, 31.3% had zero followers, 53.5% had no description and 67.3% no location. The median follower count was 3; 60.7% had fewer than 10 followers and only 4.8% had 1,000 or more.
It varies a lot by account. 80.0% of @NASA's newest followers were under 30 days old and 78.5% had never posted (median age: 0 days), which is consistent with an account that new users follow in their first session. @github was next (39.0% under 30 days, 44.0% never posted), @nvidia (30.5%, 42.5%), @Shopify (27.0%, 19.0%), @Docker (23.0%, 38.0%), @vercel (19.0%, 21.5%), @Cloudflare (16.5%, 22.5%) and @Stripe (13.0%, 11.5%). Developer-tool accounts have older, more active followers; a general-interest account gets the newcomers.
What it means for the message. Half of the people a welcome DM can reach are new to X and have never posted. A greeting that assumes they know what a thread or a Space is will land badly; a greeting that explains what your account is for, in two sentences, will not. It also means your "eligible" set skews toward fresh accounts (87% of under-30-day profiles had DMs open), so expect replies from newcomers rather than from the long-time followers you may have had in mind, and expect a share of the fresh accounts to be abandoned within weeks.

What a welcome-DM bot costs per day, both routes
Prices used: on twitterapi.io, followers at 1 to 3 credits per user returned (a full 200-record page is $0.002), profiles at 18 credits each ($0.00018), user_login_v2 at $0.10 per login, send_dm_to_user at $0.002 per call, $1 = 100,000 credits. On the official pay-per-usage rate card (docs.x.com/x-api/getting-started/pricing): $0.010 per follower read, $0.010 per user read, $0.015 per DM created, $0.010 per follow.follow webhook event. Eligibility is the 48% measured above; your account's share will differ.
At 100 new followers a day the twitterapi.io route is about $0.12 a day all in, about $2.88 a month for the DMs themselves plus a $0.10 login whenever the session expires, against $0.72 in DMs and $2.00 in reads a day on the official rate card ($0.015 vs $0.002 per DM is 7.5x). If you use the official follow webhook instead of polling, the reads become $0.010 per follow event. The poll itself adds a floor: one follower page every 10 minutes is $0.29 a day regardless of how many followed, so on a quiet account the floor dominates and a slower poll (every 30 minutes, $0.10 a day) loses nothing, since a greeting 20 minutes after the follow is still a greeting.
The 1,440-per-24-hours ceiling on the official route is far above these volumes; the binding constraint on either route is not the API but how many first-contact messages your account can send before X treats it as spam, which is why the script below defaults to a daily cap of 50 and a 90-second pause. Plans with bonus credits are on the pricing page.
Troubleshooting welcome DMs
Every follower shows can_dm: false. Expected: the follower-list field was false for all 1,600 records in our sample. Look the ids up with /twitter/user/batch_info_by_ids and read canDm there.
send_dm_to_user returns 200 but status is error. The call is billed; read message. Usual causes: the recipient does not accept message requests, the session expired (log in again, $0.10), or the account hit X's own DM limits. Add the id to the seen set; do not retry eligible-looking failures more than once.
The official API returns RecipientNotMessageableProblem. The person does not accept DMs from accounts they do not follow, or has blocked you. Skip permanently.
429 from the official API. You exceeded 15 sends in 15 minutes or 1,440 in 24 hours for the user token. Space sends out; a welcome bot should never be near either limit.
Your account was asked to verify, or DMs were locked. X judged the pattern as automation. Stop the bot, complete the verification, lower the daily cap and lengthen the pause before restarting. On Route B this is the risk the $0.002 price does not remove.
The old greeting from a 2018 app still fires. It is not controlled by anything on this page. Revoke the app under Apps and sessions, and ask on devcommunity.x.com for temporary access to welcome_messages/destroy.json if revoking does not clear it.
New followers are missed. You gained more than 200 followers between polls and the first page had no known id; raise max_pages or poll more often. Also check the handle: /twitter/user/info returns status: "error" with msg: "user not found" (HTTP 200, data: null) for a renamed account, which is what happened to one planned account in this study.
HTTP 429 from twitterapi.io. Accounts that have never paid are limited to 0.2 requests a second (20 once paid). One follower page plus one batch lookup per run is far below that.
How we collected this
Question: of the people who just followed a public organization account, how many could a welcome DM reach (DMs open to non-followers, not protected), how old are their accounts, how many have never posted, and what does that imply for daily volume and cost?
Accounts: the 8 public organization accounts @github, @vercel, @Docker, @NASA, @nvidia, @Shopify, @Cloudflare and @Stripe. @python_org was planned but /twitter/user/info returned no profile for that handle at collection time, so @Shopify took its place.
Method: GET /twitter/user/followers with userName=<account>, the newest page of 200 for each account (8 requests, 1,600 records), through a rate-limited, read-only client in 2026. Because the list's can_dm field was false for every record, the 25 newest followers of each account (200 ids) were looked up again with GET /twitter/user/batch_info_by_ids (2 requests), and canDm, protected, isAutomated, statusesCount and createdAt were read from the profile. Account age is the time from created_at to collection; buckets are under 30 days, 30 days to 1 year, 1 to 3 years, 3 to 8 years, over 8 years. 12 requests in total including the two checks on @python_org; about $0.05 in credits.
What the sample cannot show: 200 followers per account is one page at one moment, and the newest page of a large account is dominated by whoever followed in the last hours or days; the mix will differ on a developer's personal account, where followers are likelier to be established users with DMs closed. The 48% eligibility figure rests on 200 profiles, 25 per account, so per-account shares carry wide error bars. We did not send any DM, so delivery rates, reply rates and X's reaction to a given pace are not measured here. canDm is undocumented by X; our interpretation (accepts message requests from non-followers) is a reading of the data, not a specification.
Aggregates only. The 8 accounts named are organizations; every follower in the sample is a private individual, so the raw pages were kept outside the repository and discarded, and the committed sample.json holds only de-identified rows (can_dm, protected, age bucket, follower-count bucket, has description, has location, zero posts) with no id, handle, name or text. The analysis and chart scripts and results.json are kept with the study.
"""welcome_dm.py - greet people who just followed you on X (Twitter) with one direct message.
Route B: a logged-in session on twitterapi.io. The DM is sent AS YOUR ACCOUNT, so keep a human pace
(default: at most 50 greetings a day, 90 s apart). X can rate-limit, lock or suspend the account if the
pattern looks like spam. Every call that returns HTTP 200 is billed ($0.10 per login, $0.002 per DM),
including calls whose body says status: "error".
pip install requests
export TWITTERAPI_IO_API_KEY=... # twitterapi.io dashboard
export X_USER=... X_EMAIL=... X_PASSWORD=... X_TOTP_SECRET=... # your own account
export X_PROXY="http://user:pass@host:port" # a residential proxy you supply
python welcome_dm.py your_handle # one pass; run it from cron every 10-30 minutes
"""
import json
import os
import sys
import time
import requests
BASE = "https://api.twitterapi.io"
H = {"X-API-Key": os.environ["TWITTERAPI_IO_API_KEY"]}
PROXY = os.environ["X_PROXY"]
SEEN_FILE = "welcomed-or-seen.json" # every id we have greeted OR decided to skip
SESSION_FILE = "session.json" # login_cookies from /twitter/user_login_v2
DAILY_CAP = 50 # greetings per run; be conservative, this is your account
PAUSE = 90 # seconds between DMs
TEXT = ("Thanks for following! I post about building on the X API. "
"Reply 'stop' if you would rather not hear from this bot again.")
def load_json(path, default):
return json.load(open(path)) if os.path.exists(path) else default
# ---- Step 1: new followers, newest first, stop at the first known id -------------------------------
def new_followers(user_name, seen, max_pages=3):
fresh, cursor = [], ""
for _ in range(max_pages):
r = requests.get(f"{BASE}/twitter/user/followers", headers=H,
params={"userName": user_name, "cursor": cursor, "pageSize": 200}, timeout=30)
r.raise_for_status()
body = r.json()
if body.get("status") == "error":
raise RuntimeError(body.get("msg"))
for f in body.get("followers") or []:
if f["id"] in seen:
return fresh
fresh.append(f)
if not body.get("has_next_page") or not body.get("next_cursor"):
break
cursor = body["next_cursor"]
return fresh
# ---- Step 2: eligibility from the PROFILE, not from the list (its can_dm was false for all 1,600 in our study)
def eligible(followers):
out = []
for i in range(0, len(followers), 100):
ids = ",".join(f["id"] for f in followers[i:i + 100])
r = requests.get(f"{BASE}/twitter/user/batch_info_by_ids", headers=H, params={"userIds": ids}, timeout=30)
r.raise_for_status()
for u in r.json().get("users") or []: # camelCase profiles: canDm, protected, userName
if u.get("canDm") and not u.get("protected"):
out.append(u)
return out
# ---- Step 3: one login per session, then one DM per eligible follower ---------------------------------
def login():
r = requests.post(f"{BASE}/twitter/user_login_v2", headers=H, timeout=90, json={
"user_name": os.environ["X_USER"], "email": os.environ["X_EMAIL"],
"password": os.environ["X_PASSWORD"], "proxy": PROXY,
"totp_secret": os.environ.get("X_TOTP_SECRET", ""), # base32 2FA seed, strongly recommended
})
r.raise_for_status()
body = r.json() # $0.10 per call
if body.get("status") != "success":
sys.exit(f"login failed: {body.get('message')}")
json.dump({"login_cookies": body["login_cookies"]}, open(SESSION_FILE, "w"))
return body["login_cookies"]
def send_dm(cookies, user_id, text):
r = requests.post(f"{BASE}/twitter/send_dm_to_user", headers=H, timeout=60, json={
"login_cookies": cookies, "user_id": user_id, "text": text, "proxy": PROXY,
})
r.raise_for_status()
return r.json() # {"message_id", "status": "success"|"error", "message"}; billed per HTTP 200
if __name__ == "__main__":
me = sys.argv[1]
seen = set(load_json(SEEN_FILE, []))
fresh = new_followers(me, seen)
if not seen: # first run: seed, greet nobody retroactively
json.dump([f["id"] for f in fresh], open(SEEN_FILE, "w"))
sys.exit(f"seeded {len(fresh)} existing followers")
targets = eligible(fresh)
print(f"{len(fresh)} new, {len(targets)} can receive a DM", file=sys.stderr)
cookies = load_json(SESSION_FILE, {}).get("login_cookies") or login()
sent = 0
for u in targets[:DAILY_CAP]:
res = send_dm(cookies, u["id"], TEXT)
if res.get("status") != "success":
msg = (res.get("message") or "").lower()
if "login" in msg or "cookie" in msg: # session expired: one re-login, then continue
cookies = login()
res = send_dm(cookies, u["id"], TEXT)
if res.get("status") != "success":
print(f"skip @{u.get('userName')}: {res.get('message')}", file=sys.stderr)
else:
sent += 1
seen.add(u["id"]) # greeted or skipped: never try this person again
time.sleep(PAUSE)
seen.update(f["id"] for f in fresh) # ineligible followers are remembered too
json.dump(sorted(seen), open(SEEN_FILE, "w"))
print(f"{sent} welcome DM(s) sent", file=sys.stderr)
Questions readers ask
Does the Twitter welcome message API still exist?
No for every self-serve tier. The v1.1 direct_messages/welcome_messages endpoints (new, list, show, destroy, rules) are sunset; X staff on the developer forum describe them as disabled for Free, Basic and Pro plans and grant temporary access only to delete existing ones. X API v2 has no welcome-message or quick-reply endpoint, and docs.x.com's v1.1-to-v2 DM migration guide maps only events/new and events/destroy.
How do I remove an old automated welcome message from my X account?
It cannot be removed from the X app. If you still control the app that created it, call GET /1.1/direct_messages/welcome_messages/list.json and DELETE /1.1/direct_messages/welcome_messages/destroy.json?id=<id> with OAuth 1.0a; if your tier returns error 453 or the app is gone, revoke the app under Apps and sessions and open a thread on devcommunity.x.com asking for temporary access or a server-side removal, which X staff have done case by case.
What is the X API v2 equivalent of a welcome message?
There is none that fires automatically. The closest is to detect the trigger yourself (a follow via the Account Activity API's follow_events or by polling your follower list, or an inbound message via dm.received) and send a message with POST /2/dm_conversations/with/:participant_id/messages using an OAuth 2.0 user token with dm.write, dm.read, tweet.read and users.read.
How many DMs a day does the X API allow?
The documented limit for each v2 send endpoint is 15 requests per 15 minutes and 1,440 per 24 hours per user, with the same 1,440-per-24-hours ceiling per app (docs.x.com rate-limits table). We did not find the 500-a-day figure that circulates in auto-DM tool pages anywhere on docs.x.com. Each DM created is $0.015 on the pay-per-usage rate card.
Can I send a welcome DM to every new follower?
Only to those who accept message requests from accounts they do not follow and are not protected. In our sample of the 200 newest followers of 8 organization accounts, 49% had DMs open and 48% were open and not protected; the share was 87% for accounts under 30 days old and far lower for older accounts. The follower list's can_dm field was false for all 1,600 rows, so check canDm from a profile lookup.
What does a welcome-DM bot cost to run?
At 100 new followers a day on twitterapi.io: about 48 eligible DMs at $0.002 ($0.096), $0.018 in profile reads and under $0.003 in follower reads, roughly $0.12 a day, plus $0.10 per login when the session expires. The same 48 DMs on the official rate card are $0.72 plus $2.00 in follower and user reads; $0.015 vs $0.002 per DM is 7.5x. New twitterapi.io accounts that pass the bot check get up to $1 in free credits (one per person or network, expiring after 31 days); see /pricing.
Is an automated welcome DM allowed on X?
Automated DMs are governed by X's automation rules (help.x.com/en/rules-and-policies/x-automation) and by the recipient's own settings; the official v2 route is explicitly supported for sending DMs, and a session-based send acts as your account, which X can rate-limit, lock or suspend if the pattern looks like spam. Send one message, only to people who just followed you, at a human pace, with a clear way to opt out, and never as bulk outreach.
Why are so many new followers brand-new accounts?
In our sample 31% of the 1,600 newest followers of 8 organization accounts were created in the last 30 days and 35% had never posted; for @NASA the figures were 80% and 79%, consistent with an account that new users follow in their first session. Developer-tool accounts like @Stripe and @Cloudflare had older followers. Write the greeting for a newcomer.
Continue
- X API docs: Direct Messages v1.1 to v2 migration (only events/new and events/destroy are mapped; no welcome messages)
- X API v2 docs: Manage Direct Messages (three send endpoints, delete, OAuth 2.0 user context)
- X API v2 reference: POST /2/dm_conversations/with/:participant_id/messages (scopes, body, response, RecipientNotMessageableProblem)
- X API rate limits: DM send 15 per 15 min and 1,440 per 24 h per user; lookup 15 per 15 min
- X API pay-per-usage pricing: DM create $0.015, DM event read $0.010, follower and user reads $0.010, follow.follow event $0.010
- X Account Activity API v2: follow_events and unfollow_events to a registered webhook; 3 subscriptions on pay-per-use
- twitterapi.io API reference: POST /twitter/send_dm_to_user (login_cookies, user_id, text, proxy; $0.002 per call)
- twitterapi.io API reference: POST /twitter/user_login_v2 (user_name, email, password, proxy, totp_secret; $0.10 per call)
- What Twitter (X) bots are and how they work
- Build a Twitter (X) chatbot that answers DMs and mentions
- Twitter (X) auto-reply: respond to mentions and DMs automatically
- Twitter (X) webhooks: filter rules and the push payload
- Twitter (X) automation in 2026: what still works and what X allows
- Pay-per-call pricing
- twitterapi.io API reference
- 01X Communities API: How Do You Read Community Posts and Post to a Community?
- 02Twitter (X) Search Not Working? The October 2026 list: Search Outage, Other Causes and Fixes
- 03How Will You Use the Twitter (X) API or Twitter Data? Answering the Developer Sign-Up Question
- 04xurl: the X API CLI for Twitter (X) — Install, Auth, Commands and What Each Read Costs
- 05twscrape for Twitter (X): what the account pool costs you, and how to migrate to an API with no accounts
- 06Michael Saylor on Twitter — A Tracking Guide for Crypto Traders
- 07Get Twitter (X) Data in Python (2026 Tutorial)
- 08Mass Delete Tweets — Bulk API Workflow + Free Tool Options
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key