USE CASE
Twitter API for Lead Generation
Updated July 2026
How do you generate leads from Twitter (X) with an API?
Lead generation on X returns buyers by searching the language they use rather than your own product name. Run a search endpoint against five or six intent patterns, direct requests for a recommendation, competitor complaints, problem statements, switching signals and hiring posts, then resolve each author to qualify on role, recency and account quality before routing the survivors to outreach. TwitterAPIs serves every one of those reads at $0.0008 per call, so six queries polled hourly is about $3.50 a month with no developer account required. Reading prospects and then acting on them runs on one credential, which is unusual enough to be worth its own answer at reads and writes under one key.
How the numbers on this page were produced
Written by Emma, TwitterAPIs developer relations
Costs are call arithmetic against the flat $0.0008 per read call on our published pricing: queries times polls per day times $0.0008. The signal strength ranking in the first table is a working judgement from the shape of each pattern rather than a measured conversion rate, and it is labelled that way rather than dressed up as data we do not have. On yield, per our own billed-call measurement, 11.44 tweets came back on an average keyword-search call across 484,424 paying read calls from October 22 to 28, 2026 (UTC), and 29.6% of those searches returned none, so an intent query often returns a short page and still costs one call.
Six intent patterns, and what each one is worth
The mistake that makes X prospecting fail is searching for your own product name. People with a problem do not know your name yet, which is precisely why they are a lead. Search the language of the problem and the language of leaving a competitor instead.
| Pattern | Sounds like | Signal strength | Endpoint |
|---|---|---|---|
| Direct request | anyone know a good X for Y | Highest. They asked out loud, in public, right now | GET /twitter/tweet/advanced_search |
| Competitor complaint | rival tool just raised prices again | High. An existing budget and a live reason to move it | GET /twitter/tweet/advanced_search |
| Problem statement | spent all morning fixing our reporting again | Medium. The pain is real, the shopping has not started | GET /twitter/tweet/advanced_search |
| Switching signal | migrating off rival next quarter | High. A decision already made, vendor still open | GET /twitter/tweet/advanced_search |
| Rival reply threads | The pile-on under a competitor outage post | High. Concentrated unhappiness with names attached | GET /twitter/tweet/replies |
| Hiring signal | we are hiring a data engineer | Medium. Team growth often precedes tooling spend | GET /twitter/tweet/advanced_search |
Run all six as separate queries rather than one large boolean, so you keep the pattern label on every result. The label is what lets you route a direct request to outreach today and a hiring signal to nurture.
Writing an intent query that is not mostly noise
An intent query has three parts: the ask, the category, and the exclusions that keep vendors and bots out of your results. Missing the third part is what makes people conclude X prospecting does not work.
(recommend OR recommendations OR "looking for" OR suggestions)
("data warehouse" OR "etl tool" OR "reverse etl")
-filter:retweets -filter:links lang:en
-from:your_own_handle -"we build" -"check out our"- The ask is the request language itself. Keep it broad, because people phrase a request in many ways and this half is where recall lives.
- The category is the subject. Keep it tight and quoted, since this half is where precision lives.
-filter:linksis the highest-value exclusion after retweets. Most promotional posts carry a link, and most genuine questions do not.- Vendor exclusions remove your own team and the competitors marketing into the same keywords. Without them a prospecting feed fills with other vendors and reads as a dead channel.
A query written both directions matters more than it sounds. A pattern that only matches "looking for a data warehouse" misses "data warehouse recommendations?" entirely, and the second phrasing is at least as common. Put the ask and the category in separate groups rather than in a fixed order, which is what the example above does. The full operator reference is on the Twitter search API page.
Mine intent, qualify, and score
This runs the labelled patterns, resolves each author once, applies the qualification checks and returns a ranked queue. It keeps the pattern label on every row so downstream routing can treat a direct request differently from a hiring signal.
import re, requests
from datetime import datetime, timezone
BASE = "https://api.twitterapis.com/twitter"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
CATEGORY = '("data warehouse" OR "etl tool" OR "reverse etl")'
NOISE = '-filter:retweets -filter:links lang:en -"check out our"'
PATTERNS = {
"direct_request": f'(recommend OR recommendations OR "looking for") {CATEGORY} {NOISE}',
"rival_complaint": f'("rival tool") (slow OR expensive OR broken OR "switching from") -from:rivaltool {NOISE}',
"switching": f'("migrating off" OR "moving away from") ("rival tool") {NOISE}',
"problem": f'("spent all day" OR "still broken" OR "wasting hours") {CATEGORY} {NOISE}',
}
# A brand account describing a problem is marketing, not a buyer.
BRANDY = re.compile(r"\b(official|we are|we're|inc\.?|ltd|hq|team)\b", re.I)
def search(query):
res = requests.get(f"{BASE}/tweet/advanced_search",
params={"query": query, "product": "Latest"},
headers=HEADERS, timeout=30).json()
return res.get("tweets") or []
_profiles = {}
def profile(user_name):
if user_name not in _profiles: # one lookup per author, not per tweet
res = requests.get(f"{BASE}/user/info", params={"userName": user_name},
headers=HEADERS, timeout=30).json()
_profiles[user_name] = res.get("data") or {}
return _profiles[user_name]
def hours_old(created_at):
posted = datetime.strptime(created_at, "%a %b %d %H:%M:%S %z %Y")
return (datetime.now(timezone.utc) - posted).total_seconds() / 3600
def mine(role_terms, customers=frozenset(), max_age_h=48):
queue = []
for label, query in PATTERNS.items():
for tweet in search(query):
handle = tweet["author"]["userName"]
if handle.lower() in customers: # never pitch a customer
continue
age = hours_old(tweet["createdAt"])
if age > max_age_h: # intent decays fast
continue
who = profile(handle)
bio = (who.get("description") or "")
if BRANDY.search(bio):
continue
if (who.get("statusesCount") or 0) < 30: # dormant or fresh
continue
score = 0
score += {"direct_request": 40, "rival_complaint": 35,
"switching": 35, "problem": 15}[label]
score += 20 if age < 6 else (10 if age < 24 else 0)
score += 20 if any(t in bio.lower() for t in role_terms) else 0
score += 10 if (tweet.get("replyCount") or 0) == 0 else 0 # nobody replied yet
queue.append({
"score": score, "pattern": label, "handle": handle,
"age_h": round(age, 1),
"url": f"https://x.com/i/status/{tweet['id']}",
"text": tweet["text"][:160],
})
return sorted(queue, key=lambda r: -r["score"])
for lead in mine(["engineer", "analytics", "data", "cto"])[:20]:
print(lead["score"], lead["pattern"], lead["handle"], lead["url"])# Direct request: somebody asking the timeline for a recommendation
curl -G "https://api.twitterapis.com/twitter/tweet/advanced_search" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode 'query=(recommend OR "looking for") ("data warehouse" OR "etl tool") -filter:retweets -filter:links lang:en' \
--data-urlencode 'product=Latest'
# Rival complaint: an existing budget with a live reason to move it
curl -G "https://api.twitterapis.com/twitter/tweet/advanced_search" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode 'query="rival tool" (slow OR expensive OR broken) -from:rivaltool -filter:retweets lang:en' \
--data-urlencode 'product=Latest'
# Qualify the author behind a promising tweet
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.twitterapis.com/twitter/user/info?userName=prospect_handle"const BASE = "https://api.twitterapis.com/twitter";
const HEADERS = { Authorization: "Bearer YOUR_API_KEY" };
const profiles = new Map();
const get = async (path, params) => {
const url = new URL(`${BASE}/${path}`);
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
return (await fetch(url, { headers: HEADERS })).json();
};
async function profile(userName) {
if (!profiles.has(userName)) {
profiles.set(userName, (await get("user/info", { userName })).data ?? {});
}
return profiles.get(userName);
}
const WEIGHTS = { direct_request: 40, rival_complaint: 35, problem: 15 };
async function mine(patterns, roleTerms, maxAgeH = 48) {
const queue = [];
for (const [label, query] of Object.entries(patterns)) {
const res = await get("tweet/advanced_search", { query, product: "Latest" });
for (const tweet of res.tweets ?? []) {
const ageH = (Date.now() - Date.parse(tweet.createdAt)) / 3.6e6;
if (ageH > maxAgeH) continue;
const who = await profile(tweet.author.userName);
if ((who.statusesCount ?? 0) < 30) continue;
const bio = (who.description ?? "").toLowerCase();
const score =
(WEIGHTS[label] ?? 10) +
(ageH < 6 ? 20 : ageH < 24 ? 10 : 0) +
(roleTerms.some((t) => bio.includes(t)) ? 20 : 0);
queue.push({ score, label, handle: tweet.author.userName, id: tweet.id });
}
}
return queue.sort((a, b) => b.score - a.score);
}
console.log(await mine({ direct_request: '(recommend) ("etl tool") -filter:retweets' },
["engineer", "data"]));The profile cache is what keeps this affordable: one busy handle matching three patterns costs one lookup rather than three. The zero-replies bonus is the part worth stealing, because a request nobody has answered yet is worth several that already collected four vendor replies.
Six qualification checks before anyone gets contacted
| Check | Where it comes from | Why it earns its place |
|---|---|---|
| Recency | createdAt on the tweet | A buying-intent tweet decays fast. A week-old request has usually been answered by somebody else |
| Account is a person, not a brand | user/info profile fields | A company account posting about a problem is marketing. A named individual is a human with a budget |
| Role fit | Bio text on the profile | The cheapest qualification signal you have, and it is already in the payload |
| Account is real and active | Post count and account age | Filters the fresh and dormant accounts that make an outreach list look larger than it is |
| Not already a customer | Your own CRM, matched on handle | The check nobody builds until the first time somebody pitches an existing customer |
| Thread context | tweet/replies on the post | If four vendors already replied, you are late, and the reply is worth less than the research |
Five of the six read fields that are already in the payload, so qualification costs one profile call per unseen author and nothing more.
Dedupe and score in your code
The API returns posts and profiles, not a lead score, and that is deliberate: the weights that decide who is worth a reply are a property of your market, not ours. Keep them in your own code, where you can change them after a week of results. Two keys do the deduping. The post ID stops one request being queued twice when it matches two patterns, and the numeric author ID stops one person being contacted twice across a week of different posts. Never dedupe on the handle, because a renamed account would come back as a stranger.
A workable first scorecard has four inputs, all already in hand after the qualification pass: the pattern label, with direct requests and rival complaints weighted highest; the age of the post in hours; the number of replies it already has, where zero earns a bonus; and a bio match on the roles you sell to. Log the score next to the outcome of every contact. After a few weeks the log tells you which weight is wrong, which no default from a vendor ever could.
Route qualified leads to your CRM or Slack
Route on the pattern label rather than on the score alone, because the label says how fast someone has to act. A direct request or a rival complaint goes to a Slack channel the same hour, with the post text, the author's bio and a link to the thread, since its value is gone in a day or two. Problem statements and hiring signals go to the CRM as nurture records, created or updated with the author ID as the dedupe key, where they can wait weeks without losing much. Anything that failed a qualification check goes nowhere at all, which is the routing rule that protects reply rate the most.
Prospects from engagement that converts
Search finds people who say what they need. Engagement finds the people who showed interest without saying it, and the strongest version is engagement with your own posts. Everyone who replied to a launch post comes from GET /twitter/tweet/replies, everyone who quoted it from GET /twitter/tweet/quotes, and everyone who reposted it from GET /twitter/tweet/retweeters. Run the same three reads on a competitor's launch or pricing post and you have a list of people evaluating your category this week.
Rank them by effort. A reply or a quote means someone wrote words, and a question in a reply is close to a direct request. A repost is a weaker signal on its own, so treat reposters as an audience to qualify rather than as leads. Every engager then goes through the same profile check as a search match, one read per unseen author, so a launch post with a few hundred engagers costs well under a dollar to work through. For the query side of this, the search operators guide covers the syntax the intent patterns above are built from.
A bad reply, and a better one
Finding the post is the cheap part. What converts is the reply, and most automated prospecting fails here rather than in the search. Take a post asking whether anyone knows an ETL tool that a three-person data team can run without a dedicated engineer.
The bad reply names the product in the first three words, calls it the best on the market, and ends with a demo link. It answers a question nobody asked, it reads as automated even when it is not, and the original poster has usually seen three like it already.
The better reply answers the actual question first: for a team that size, the cost that bites is usually connector maintenance rather than the licence, so ask any vendor how schema changes upstream are handled. Then it discloses the affiliation in plain words and offers to share how one tool handles that, with no link unless asked. It is longer, slower to write, and it is the one that gets a reply back. Automate the finding and the context gathering; keep a person on the sending.
Who runs this loop
B2B SaaS and startup founders
Early on, the founder is the sales team, and X is often where the first customers are already talking. The loop on this page fits a founder's week: a handful of qualified posts a day, read over coffee, answered personally. At about $6 a month for the full setup it costs less than one seat on most prospecting tools, and because it is your own code, the intent patterns can track the exact language your early customers used when they found you.
Agencies and lead-gen services
An agency runs the same loop once per client, each with its own category terms, exclusion list and routing. Billing per call with no seat fee means ten client loops cost ten times one loop, and nothing extra for the analysts who read the output. Keep each client's queries and dedupe store separate, so a prospect who fits two clients is never pitched twice in one week, and report per client on posts found, qualified and answered rather than on raw matches.
What a prospecting loop costs a month
| Component | Calls | Cost | Shape |
|---|---|---|---|
| Six intent queries, hourly | 144 calls a day, about 4,380 a month | About $3.50 a month | The standing prospecting loop |
| Qualify 1,000 candidates a month | About 1,000 profile calls | About $0.80 | One profile lookup per unseen author |
| Thread context on the top 200 | About 200 calls | About $0.16 | Only on candidates that already cleared scoring |
| Rival complaint mining, 3 rivals hourly | 72 calls a day, about 2,190 a month | About $1.75 a month | The highest-conversion lane in most setups |
The whole setup is roughly $6 a month. Tightening the intent loop from hourly to every 15 minutes takes the first row from about $3.50 to about $14, which is the only lever here that meaningfully moves the bill. Model it on the cost calculator.
What this will not do for you
It does not return email addresses. The API returns public posts and public profile fields. There is no contact detail in the payload, so the reply path is a public reply or a direct message, not an email sequence.
Volume is smaller than you expect, and that is correct. A tight intent query in a niche category returns a handful of genuinely qualified people a day, not hundreds. A query returning hundreds is almost always matching vendors and noise, and the fix is a better exclusion list rather than a bigger list.
Speed matters more than coverage. A public request is usually answered within hours. Being second is worth much less than being first, which is why cadence is the lever worth spending on rather than query count.
Collection and outreach are different decisions. Reading public data is one question. What you may send, to whom, and with what disclosure is governed by the platform terms and by the law where you and the recipient are. Take advice on that before sending at volume rather than after.
By the numbers
Prospecting on X, by the numbers
TwitterAPIs figures resolve to our published pricing. Every X figure is vendor-documented.
TwitterAPIs bills every search and profile call at a flat $0.0008, with no plan tier and no per-lead fee. (TwitterAPIs pricing, 2026)
Six intent queries polled hourly is 144 calls a day, about $3.50 a month at the published rate. (TwitterAPIs pricing, 2026)
Tightening the same six queries from hourly to every 15 minutes is 576 calls a day, which moves the monthly figure from about $3.50 to about $14. (TwitterAPIs pricing, 2026)
A new account starts with $0.50 in credit and no card, enough to run a six-query intent loop hourly for several days before you spend anything. (TwitterAPIs pricing, 2026)
The official X API meters post reads from about $0.005 per resource, one item per request, under its pay-per-use model. (X Developer Platform, 2026)
The official X API enforces per-endpoint rate limits in fixed 15-minute windows, which bounds how tight a prospecting loop can run. (X API docs, 2026)
Lead generation, common questions
Search for the language buyers use rather than for your own product name. Run GET /twitter/tweet/advanced_search on five or six intent patterns: direct requests for a recommendation, complaints about a competitor, problem statements, switching signals, and hiring posts that imply growth. Resolve each author through GET /twitter/user/info to qualify on role and account quality, then score and route the survivors. Six queries polled hourly is 144 calls a day, about $3.50 a month at a flat $0.0008 a call.
Six checks, and five of them use data already in the payload. Check recency, because intent decays within days. Check that the author is a person rather than a brand account. Read the bio for role fit. Check post count and account age to drop dormant or fresh accounts. Match against your own CRM so nobody pitches an existing customer. Then pull the reply thread on the highest scorers, because if four vendors already answered, you are late.
Yes, and it is usually the highest-converting lane. Run advanced_search on the rival's name and handle with negative language and an exclusion for their own account, so you get customers rather than the company's own posts. Then pull GET /twitter/tweet/replies under any competitor post that is collecting unhappy responses, since a pile-on concentrates a lot of qualified unhappiness in one place with names attached.
No. TwitterAPIs authenticates with one Bearer token from signup, with no X developer application, no app review and no OAuth flow to implement. Signup includes $0.50 in credit with no card on file, which covers a six-query intent loop for several days before you spend anything.
Yes, though you write that step yourself. The API returns JSON, not a CRM integration, so the normal shape is a small worker that runs the queries, drops the survivors into your own table, and calls your CRM's create-or-update endpoint with the author id as the dedupe key. Keying on the numeric author id rather than the handle matters, because a handle change would otherwise create a second record for a prospect you are already talking to.
Audience discovery asks who is worth following, and it is answered from follower graphs and topical overlap, so it is a slow, standing question. Lead generation asks who is in market right now, and it is answered from what somebody posted in the last few hours, so it decays. The practical difference is cadence and shelf life: an audience list is worth rebuilding monthly, an intent list is stale within about two days.
Four cases, and skipping them protects reply rate more than any extra query would. Ignore a post already carrying several vendor replies, because you are late and the thread shows it. Ignore brand and support accounts, which match the language without being buyers. Ignore anything past about forty-eight hours, since the decision has usually been made. And ignore an author already in your CRM, because pitching an existing customer costs more than the lead was worth.
In practice, two patterns beat the rest. The first is somebody asking the timeline for a recommendation, because they have announced both the need and the timing in public. The second is a complaint about a competitor, because it implies an existing budget and a live reason to move it. Problem statements and hiring posts are worth collecting but sit earlier in the cycle, so they belong in a nurture list rather than in a same-day outreach queue.
A standing loop of six intent queries polled hourly is 144 calls a day, roughly $3.50 a month. Qualifying a thousand candidates adds about a thousand profile calls, near $0.80. Adding rival complaint mining across three competitors at hourly cadence is another 72 calls a day, about $1.75 a month. The full setup lands around $6 a month, with no plan tier and no per-lead fee.
Fresher than most teams assume. A public request for a recommendation is usually answered within hours, so a lead found the next day is competing against replies that already landed. Poll intent queries at least hourly, and treat anything older than about 48 hours as nurture rather than outreach. Because billing is per call, tightening that loop is arithmetic rather than a plan upgrade: hourly to every 15 minutes on six queries takes the monthly cost from about $3.50 to about $14.
Five fields, and four of them come back in the payload you already paid for. The post itself, with its id and timestamp, so the reason for contact is quotable. The author handle and numeric id, because handles change and ids do not. The bio and follower count, which is what role fit is judged on. The intent pattern that matched, so a rep knows whether this is a recommendation request or a rival complaint before they open it. And a link back to the thread, because the replies decide whether you are first or fifth. A record that carries only a URL makes somebody re-read the post to find out why it was collected.
The write endpoints exist, so it is technically one integration rather than two: the catalog covers posting, following, liking and sending a direct message alongside the read endpoints this page uses. Whether you should is a different question. Automated DM volume is the fastest way to get an account limited, and a recommendation request answered by an obvious bot converts worse than no reply. The pattern that works is automated FINDING and human sending.
Yes, and it is the highest-converting pattern on this page. Run advanced_search for the phrasings people actually use, which are casual: asking the timeline for suggestions, asking whether anyone has moved off a named tool, or asking what to use instead of one. Search for that language rather than for your own product name, because a buyer who already knows your name is not the one you are missing.
The API returns public posts and public profile fields only, the same data anyone browsing x.com can see, and it does not return email addresses or any private contact detail. What you may then do with that data is a separate question governed by the platform terms and by the privacy and marketing law where you and the recipient are, and those rules differ by region. Treat collection and outreach as two different decisions, and take advice on the second one before you send at volume.
Related use cases
The query syntax the intent patterns above rely on is documented in the advanced search operator reference, and the build-or-buy arithmetic behind running this loop yourself is worked through in the competitor-analysis build-vs-buy comparison. The rival-complaint lane is the outreach half of competitor analysis, and the same category queries that surface buyers also power social listening. All fourteen workloads are indexed on the Twitter API use cases hub.
Find this week's buyers before your competitors reply
$0.0008 per call, $0.50 in free credit, no card and no X developer account.
Next read
Continue exploring related pages:
Twitter search API
Real-time search with operators via the advanced_search endpoint, $0.0008 a call, $0.04 per 1,000 tweets on full 20-tweet pages.
Twitter API use cases
14 use cases from sentiment analysis to lead generation.
Twitter API v2 pricing vs TwitterAPIs
Side-by-side endpoints, pricing, auth, and response shape, same data, 100x cheaper.