Skip to main content
RapidDev - Software Development Agency
API AutomationsX (Twitter)

How to Automate X (Twitter) Replies using the API

Automated replies on X use POST /2/tweets with a reply.in_reply_to_tweet_id field. Monitor mentions or keyword matches via GET /2/tweets/search/recent (7-day window, Basic tier required), then reply to relevant tweets. Key risk: error 226 (spam detection) fires aggressively on rapid or repetitive replies — vary text and add human-like delays. Cost on pay-per-use: $0.015 per text reply.

Need help automating? Talk to an expert
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate5 min read3–5 hoursX (Twitter)Last updated May 2026RapidDev Engineering Team
TL;DR

Automated replies on X use POST /2/tweets with a reply.in_reply_to_tweet_id field. Monitor mentions or keyword matches via GET /2/tweets/search/recent (7-day window, Basic tier required), then reply to relevant tweets. Key risk: error 226 (spam detection) fires aggressively on rapid or repetitive replies — vary text and add human-like delays. Cost on pay-per-use: $0.015 per text reply.

Quick facts about this guide
FactValue
PlatformX (Twitter)
Rate limits180 search requests per 15 minutes (Basic tier)
DifficultyIntermediate
Time required3–5 hours
Last updatedMay 2026

API Quick Reference

Auth

Rate limit

180 search requests per 15 minutes (Basic tier)

Format

SDK

REST only

API overview

Base URLhttps://api.x.com/2

Authentication

  1. 1Go to developer.x.com and create a Project and App
  2. 2In App settings, enable OAuth 2.0 and set a callback URL (e.g., https://yourapp.com/callback)
  3. 3Request scopes: tweet.read, tweet.write, users.read
  4. 4Redirect the user to https://twitter.com/i/oauth2/authorize with code_challenge (PKCE)
  5. 5After user approves, exchange the authorization code for access and refresh tokens at POST /2/oauth2/token
  6. 6Store tokens securely — access token expires in 2 hours, refresh with POST /2/oauth2/token (grant_type=refresh_token)
typescript
1import requests
2import base64
3import hashlib
4import os
5
6# Exchange auth code for tokens
7def get_tokens(code, code_verifier, client_id, redirect_uri):
8 resp = requests.post(
9 'https://api.x.com/2/oauth2/token',
10 data={
11 'code': code,
12 'grant_type': 'authorization_code',
13 'client_id': client_id,
14 'redirect_uri': redirect_uri,
15 'code_verifier': code_verifier
16 }
17 )
18 return resp.json() # {'access_token': '...', 'refresh_token': '...', 'expires_in': 7200}
19
20# Refresh expired access token
21def refresh_access_token(refresh_token, client_id):
22 resp = requests.post(
23 'https://api.x.com/2/oauth2/token',
24 data={
25 'grant_type': 'refresh_token',
26 'refresh_token': refresh_token,
27 'client_id': client_id
28 }
29 )
30 return resp.json()

Security notes

  • Never hardcode tokens in source code — use environment variables
  • Access tokens expire in 2 hours; implement automatic refresh before expiry
  • The refresh token does not expire automatically but regenerate every 90 days as a security practice
  • Store the user's access token per-account if building a multi-account tool
  • Reading mentions uses the same OAuth 2.0 Bearer token as posting — no dual auth needed for v2

Key endpoints

GET/2/tweets/search/recent

Search tweets from the past 7 days. Use to find mentions or keyword matches to reply to. Requires Basic tier or higher.

ParameterTypeRequiredDescription
querystringrequiredSearch query, e.g., '@yourbrand' or 'keyword -is:retweet'
tweet.fieldsstringoptionalFields to return: id,text,author_id,created_at,public_metrics
max_resultsintegeroptionalResults per page, 10–100
since_idstringoptionalReturn tweets after this tweet ID for incremental polling

Request

json
1GET https://api.x.com/2/tweets/search/recent?query=%40yourbrand%20-is%3Aretweet&tweet.fields=id%2Ctext%2Cauthor_id%2Ccreated_at&max_results=100&since_id=1234567890
2Authorization: Bearer YOUR_ACCESS_TOKEN

Response

json
1{
2 "data": [
3 {
4 "id": "1790000000000000001",
5 "text": "@yourbrand how do I reset my password?",
6 "author_id": "98765432",
7 "created_at": "2026-04-20T10:15:30.000Z"
8 }
9 ],
10 "meta": {
11 "newest_id": "1790000000000000001",
12 "oldest_id": "1789000000000000001",
13 "result_count": 1
14 }
15}
GET/2/users/{id}/mentions

Retrieve recent tweets mentioning the authenticated user. Requires Basic tier. Paginates via pagination_token.

ParameterTypeRequiredDescription
idstringrequiredThe authenticated user's Twitter ID (not username)
tweet.fieldsstringoptionalFields: id,text,author_id,created_at,conversation_id
since_idstringoptionalReturn mentions after this ID for polling
max_resultsintegeroptional5–100 results per page

Request

json
1GET https://api.x.com/2/users/123456789/mentions?tweet.fields=id%2Ctext%2Cauthor_id%2Ccreated_at&since_id=1789000000000000001&max_results=100
2Authorization: Bearer YOUR_ACCESS_TOKEN

Response

json
1{
2 "data": [
3 {
4 "id": "1790000000000000002",
5 "text": "@yourbrand love your product!",
6 "author_id": "11223344",
7 "created_at": "2026-04-20T11:00:00.000Z"
8 }
9 ],
10 "meta": {"newest_id": "1790000000000000002", "result_count": 1}
11}
POST/2/tweets

Post a reply to a tweet. Include reply.in_reply_to_tweet_id in the request body. Requires user-context OAuth 2.0 (cannot use app-only Bearer token).

ParameterTypeRequiredDescription
textstringrequiredReply text, max 280 characters
reply.in_reply_to_tweet_idstringrequiredID of the tweet you are replying to

Request

json
1POST https://api.x.com/2/tweets
2Authorization: Bearer USER_ACCESS_TOKEN
3Content-Type: application/json
4
5{
6 "text": "Hi! Happy to help. Please check our password reset page at support.example.com",
7 "reply": {
8 "in_reply_to_tweet_id": "1790000000000000001"
9 }
10}

Response

json
1{
2 "data": {
3 "id": "1790000000000000099",
4 "text": "Hi! Happy to help. Please check our password reset page at support.example.com"
5 }
6}

Step-by-step automation

1

Poll for new mentions and keyword matches

Why: There is no webhook for new mentions in X API v2 (filtered stream exists but requires Basic+ and is more complex). Simple polling with since_id ensures you never process the same tweet twice.

Store the ID of the most recently seen tweet in your state file or database. Each poll cycle, pass since_id with that value. X returns only tweets newer than that ID, so your processed-IDs set stays small.

curl
1# Initial poll (no since_id)
2curl -s 'https://api.x.com/2/tweets/search/recent?query=%40yourbrand+-is%3Aretweet&tweet.fields=id%2Ctext%2Cauthor_id%2Ccreated_at&max_results=100' \
3 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
4
5# Subsequent polls with since_id
6curl -s 'https://api.x.com/2/tweets/search/recent?query=%40yourbrand+-is%3Aretweet&tweet.fields=id%2Ctext%2Cauthor_id%2Ccreated_at&max_results=100&since_id=1790000000000000001' \
7 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Pro tip: Use -is:retweet and -is:reply in your query to avoid replying to retweets and existing reply threads, which can trigger spam detection.

Expected result: Returns only tweets newer than the last processed ID. On first run, returns up to 100 recent matching tweets.

2

Filter and classify tweets that need a reply

Why: Not every mention warrants an automated reply. Replying to everything — especially promotional mentions or retweets of your content — wastes API budget and risks error 226. A simple keyword or intent classifier reduces noise.

Check tweet text against keyword categories: questions (contains '?', 'how', 'what', 'when', 'help', 'can you'), complaints, or general positive mentions. Skip retweets, quotes of your content, and tweets from your own account. Maintain a replied-IDs set to prevent duplicate replies.

curl
1# No direct curl equivalent this is pure logic step
2# Just verify your filter before posting a reply
3curl -s 'https://api.x.com/2/tweets/1790000000000000001?tweet.fields=text,author_id' \
4 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Pro tip: Keep a minimum of 5–8 different reply templates per category and rotate randomly. Identical or near-identical replies are the primary trigger for error 226.

Expected result: Each tweet is categorized as 'question', 'complaint', 'positive', or null (skip). Only categorized tweets proceed to the reply step.

3

Post the reply with human-like timing

Why: X's spam detection (error 226) has no published threshold, but it fires when replies come too fast or follow a repetitive pattern. Adding a random delay between 8–30 seconds per reply and varying the reply text significantly reduces the risk.

Post the reply using POST /2/tweets with reply.in_reply_to_tweet_id. Check the response for error code 226 (spam) and error 187 (duplicate status). After posting, wait a randomized delay before the next reply. Log each successful reply with the tweet ID.

curl
1curl -s -X POST 'https://api.x.com/2/tweets' \
2 -H 'Authorization: Bearer USER_ACCESS_TOKEN' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "text": "Happy to help! Could you share more details so we can assist you?",
6 "reply": {
7 "in_reply_to_tweet_id": "1790000000000000001"
8 }
9 }'

Pro tip: Process tweets from oldest to newest (reverse the array). This maintains chronological conversation order and looks more natural to X's spam detection.

Expected result: Each qualifying tweet receives a categorized, randomized reply. The bot pauses 8–30 seconds between replies and handles error 226 by pausing 30 minutes.

4

Handle rate limits and monitor API costs

Why: On pay-per-use, every reply costs $0.015 (text) or $0.20 (link). Reading mentions costs $0.005 per read. At 100 mentions polled every 5 minutes, you can accumulate $7.20/hour in read costs alone. Budget tracking is essential.

Read the x-rate-limit-remaining and x-rate-limit-reset headers on every response. When remaining drops below 20% of limit, pause until reset. Track total reply count and estimated cost per session. Log to a file or database for billing analysis.

curl
1# Check rate limit headers in curl response
2curl -s -I 'https://api.x.com/2/tweets/search/recent?query=%40yourbrand&max_results=10' \
3 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
4 | grep -i 'x-rate-limit'

Pro tip: Set a daily cost cap (e.g., $5) and stop the bot when it's reached. On pay-per-use, a misconfigured query that matches thousands of tweets can drain budget in minutes.

Expected result: The bot never hits a hard 429. Cost tracking prevents runaway charges. Session summary shows total spend per run.

Complete working code

Complete automated reply bot that polls for mentions, classifies them, and replies with randomized templates. Includes rate limiting, cost tracking, and spam detection handling.

Error handling

Cause

X's spam detection triggered. Fires on rapid replies, repetitive text, or unusual posting patterns.

Fix

Cause

You attempted to post the same text twice, even to different tweets.

Fix

Cause

Exceeded 180 search requests per 15 minutes (Basic) or 2,400 posts per day account limit.

Fix

Cause

Access token expired (2-hour lifetime) or is invalid.

Fix

Cause

Attempted to use app-only Bearer token for posting. POST /2/tweets requires user-context OAuth 2.0.

Fix

Rate limits & throttling

ScopeLimitWindow

Security checklist

  • Store access and refresh tokens in environment variables, never in source code
  • Validate tweet content before replying — never echo user input back in replies
  • Implement a per-session cost cap to prevent runaway API spend on pay-per-use
  • Keep a replied-IDs log to prevent double-replying the same tweet
  • Never reply to tweets from your own account (check author_id against your user ID)
  • Add -is:retweet and -is:reply to search queries to avoid entering existing reply chains
  • Rotate reply templates to avoid duplicate status errors and spam detection
  • Implement an emergency kill switch (environment variable) that halts the bot immediately

Automation use cases

Best practices

  • Use -is:retweet -is:reply in your search query to only reply to original tweets mentioning you, not retweet chains
  • Keep at least 8–10 reply templates per category and select randomly to avoid error 226
  • Add a random delay of 8–30 seconds between replies — uniform delays look like automation
  • Never include URLs in replies unless necessary — link replies cost $0.20 each vs $0.015 for text
  • Poll every 5 minutes rather than every minute — Basic tier gives 180 search requests per 15 min, daily polling at 1-min intervals would exhaust this
  • Monitor x-rate-limit-remaining on every response and preemptively pause when below 20% of limit
  • Log every reply with the original tweet ID, reply ID, category, and timestamp for audit and debugging

Ask AI to help

Copy one of these prompts to get a personalized, working implementation.

ChatGPT / Claude Prompt

I'm building an automated Twitter/X reply bot using the X API v2. The bot monitors mentions via GET /2/tweets/search/recent and replies using POST /2/tweets with reply.in_reply_to_tweet_id. I need help writing a keyword classifier that categorizes mentions into 'question', 'complaint', and 'positive' categories, then selects from a set of reply templates. The bot should respect error 226 (spam detection) by pausing 30 minutes and use random delays of 8–30 seconds between replies. Please write this in Python with proper rate limit handling.

Lovable / V0 Prompt

Create a dashboard for monitoring my X (Twitter) reply bot. It should show: total replies sent today, estimated API cost (at $0.015/text reply), a list of recent replies with tweet preview and category, and a kill switch toggle to pause/resume the bot. Connect it to a Supabase table that the bot writes to after each successful reply.

Frequently asked questions

Do I need Basic tier ($200/month) to automate replies?

Yes, for practical use. The Free tier is effectively closed to new developers as of February 2026 and only allows 1 request per 24 hours on most read endpoints. Basic at $200/month gives you 180 search requests per 15 minutes and 50,000 writes/month. Alternatively, pay-per-use charges $0.015 per text reply and $0.005 per search read — calculate which is cheaper based on your expected volume.

What triggers error 226 (spam detection) and how do I avoid it?

Error 226 has no publicly documented threshold, but it fires on three main patterns: replying too fast (under 8–10 seconds between replies), sending identical or near-identical text to multiple users, and replying to a large number of tweets in a short window. Mitigation: randomize delays 8–30 seconds, maintain at least 8 different reply templates per category, and pause for 30 minutes when 226 occurs.

Can I use app-only Bearer token to post replies?

No. POST /2/tweets requires user-context OAuth 2.0 (Authorization Code with PKCE). App-only Bearer token is read-only and can fetch mentions and search results, but cannot post. You need the user's personal access token obtained via the PKCE flow.

My access token expires in 2 hours — how do I handle this in a long-running bot?

Store both the access token and refresh token. Before each API call, check if the access token's expiry time (current time + 7200 seconds from when it was issued) is within 5 minutes of expiring. If so, call POST /2/oauth2/token with grant_type=refresh_token to get a fresh access token. The refresh token itself does not expire but should be rotated every 90 days.

Can I reply to tweets mentioning keywords even if I'm not tagged?

Yes. Use GET /2/tweets/search/recent with a keyword query like 'your_keyword -is:retweet' instead of '@youraccount'. You must be on Basic tier — this endpoint is restricted on Free. Be aware that responding to keyword matches (not direct mentions) increases spam detection risk, as you're engaging with users who didn't explicitly address you.

How can RapidDev help with building my X reply automation?

RapidDev can help you build the underlying infrastructure for your X reply bot — database schemas, token refresh workers, cost tracking dashboards, and Supabase integration for logging replied tweets. If you're building with Lovable or V0, we have step-by-step guides for connecting your bot backend to a visual dashboard.

RapidDev

Need this automated?

Our team has built 600+ apps with API automations. We can build this for you.

Book a free consultation

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.