By Sorsa Editorial

Updated June 2026: refreshed the official X API cost comparison for the April 2026 per-resource pricing and re-verified all six endpoint methods against the live API.

Key Takeaway: Finding your target audience on X (Twitter) at scale means pulling user lists programmatically instead of scrolling. Six API methods cover it: bios by keyword, live tweet intent, a competitor's followers, a Community's members, verified followers, and post amplifiers. Each returns full public profiles you then deduplicate, filter, and export.

The bottleneck is rarely the strategy. It is access to the data. X shows a handful of followers in the UI before the list stops loading, and the official X API now bills per resource: every follower profile you read costs $0.010, so a single 10,000-follower pull runs about $100 before you have filtered anyone. Sorsa API, an alternative Twitter/X API provider, removes both walls. Its /followers endpoint returns up to 200 full profiles per request on a flat per-request price, so the same 10,000 followers cost roughly ten cents on the Pro plan, and you authenticate with a single ApiKey header rather than an OAuth handshake and an approval queue. Every technique below uses one endpoint and runs at a flat 20 requests per second on any plan.

This guide covers the six methods we use to assemble an audience, the code to run each, how to combine and clean the results, what it costs, and what to do with the list once you have it.

Table of Contents

What does audience discovery on X actually mean? {#what-does-audience-discovery-on-x-actually-mean}

Audience discovery on X (Twitter) is the process of identifying the specific accounts worth reaching, then collecting them as a structured, filterable list. It spans three overlapping groups: people who publicly describe themselves as your audience, people who already follow accounts in your space, and people actively posting about your topic right now.

The manual route is familiar. You open Advanced Search, build a few Twitter Lists by hand, browse a Community, and keep notes. That works for a one-off look. It falls apart the moment you need a few thousand accounts, the data points to qualify them, and a process you can rerun next month. There is no export button, the UI stops loading after a few hundred profiles, and none of it is reproducible.

The programmatic route fixes all three problems. Each discovery question maps to a single API call that returns full public profiles as clean JSON: bio, follower count, account age, location, verified status. You collect at scale, filter in code, and export to CSV or a database. The rest of this guide is the practical version of that.

One distinction worth keeping straight: your target audience is who you want to reach, which is not always who already follows you. The methods below find the former, not just the latter.

Six ways to find an audience, mapped to the question each answers {#six-ways}

Each technique answers a different targeting question and uses a different endpoint. Run one in isolation, or combine several and score by overlap (covered further down).

#The question it answersEndpointProfiles per request
1Who identifies as your target persona in their bio?/search-users~20
2Who already follows an account in your space?/followersup to 200
3Who joined a Community around your topic?/community-members~20
4Who is actively discussing your topic now?/search-tweets~20
5Which verified accounts follow a target?/verified-followersup to 200
6Who amplifies content in your space?/retweeters, /quotes~20

All examples use https://api.sorsa.io/v3 as the base URL and the ApiKey header. The shared setup:

python
import requests, time

API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
AUTH = {"ApiKey": API_KEY}                                  # GET endpoints
POST_HEADERS = {**AUTH, "Content-Type": "application/json"} # POST endpoints

Technique 1: Find accounts by what they put in their bio {#technique-1}

The fastest way to find people who self-declare as your audience is to search what they wrote about themselves. A keyword like Solidity developer, growth marketer, or oncology nurse matches against bios, display names, and handles, so the people who identify with a role surface directly. This is the right call when your audience labels itself with a title or niche.

The /search-users endpoint takes a query and paginates through matches. Each result is a full profile, so you qualify candidates in code without a second lookup. See the search-users endpoint reference for the response schema.

python
def find_users_by_bio(query, max_pages=10):
    users, cursor = [], None
    for _ in range(max_pages):
        body = {"query": query}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(f"{BASE}/search-users", headers=POST_HEADERS,
                             json=body, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        users.extend(data.get("users", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return users

users = find_users_by_bio("machine learning engineer")

The pattern is identical in any language. The same call in JavaScript:

javascript
async function findUsersByBio(query, maxPages = 10) {
  const all = [];
  let cursor = null;
  for (let i = 0; i < maxPages; i++) {
    const body = { query };
    if (cursor) body.next_cursor = cursor;
    const resp = await fetch("https://api.sorsa.io/v3/search-users", {
      method: "POST",
      headers: { ApiKey: "YOUR_API_KEY", "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    const data = await resp.json();
    all.push(...(data.users || []));
    cursor = data.next_cursor;
    if (!cursor) break;
    await new Promise((r) => setTimeout(r, 100));
  }
  return all;
}

Because every record carries the full profile, filtering is a list comprehension, not another round of API calls:

python
qualified = [
    u for u in users
    if u.get("followers_count", 0) >= 1000
    and u.get("tweets_count", 0) >= 100
    and not u.get("protected", False)
]

For multi-word and operator-driven precision, the same query syntax used for tweets applies. Our Twitter Search API guide walks through the operator set in depth.

Technique 2: Extract a competitor's follower list {#technique-2}

The single highest-yield audience signal is who already follows an account in your space. If a person follows your direct competitor, they have pre-qualified themselves: they care about the category. The /followers endpoint returns that list, up to 200 full profiles per request, which makes it the most cost-efficient discovery call available.

python
def get_followers(username, max_pages=20):
    followers, cursor = [], None
    for _ in range(max_pages):
        params = {"username": username}
        if cursor:
            params["next_cursor"] = cursor
        resp = requests.get(f"{BASE}/followers", headers=AUTH,
                            params=params, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        followers.extend(data.get("users", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return followers

followers = get_followers("competitor_handle", max_pages=20)

A single competitor is a starting point. The accounts that follow several players in your space are a sharper segment, because overlap signals genuine category interest rather than a one-off follow. Pull a few seeds and keep the intersection:

python
from collections import Counter

competitors = ["competitor_a", "competitor_b", "competitor_c"]
follower_sets = {h: {f["id"] for f in get_followers(h, max_pages=10)}
                 for h in competitors}

all_ids = [uid for ids in follower_sets.values() for uid in ids]
overlap = {uid for uid, c in Counter(all_ids).items() if c >= 2}

Walking a 50-million-follower account end to end is rarely worth it. For accounts over roughly 500,000 followers, pulling the first 50 to 100 pages (10,000 to 20,000 profiles) is usually a representative sample of the recent, active audience. For a deeper treatment of follower and following extraction, including the protected-account and sampling edge cases, see our guides on extracting Twitter followers and the Twitter Followers API, plus the followers endpoint reference.

Technique 3: Pull a Community's member roster {#technique-3}

X Communities are topic-scoped groups, which means the membership is already filtered by self-selected interest. Pulling the roster of a Community in your niche hands you an audience that opted in to the exact subject. The /community-members endpoint accepts either the numeric Community ID or the full URL.

python
def get_community_members(community_id, max_pages=20):
    members, cursor = [], None
    for _ in range(max_pages):
        body = {"community_link": community_id}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(f"{BASE}/community-members", headers=POST_HEADERS,
                             json=body, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        members.extend(data.get("users", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return members

Community results are compact profiles (id, username, display name, image, verified, protected). To enrich them with follower counts, bios, and account age for filtering, pass the collected IDs through the batch profile endpoint, which accepts up to 100 IDs and counts as a single request. The related Lists and Communities endpoints are documented in our X List and Communities API guide and the lists and communities docs.

Technique 4: Mine real-time intent from tweet search {#technique-4}

The other techniques find people by who they are or who they follow. Tweet search finds them by what they are saying right now. Someone posting "looking for a CRM recommendation" today is a different and often more valuable audience than someone who merely fits a static profile, because the intent is fresh. Search the tweets, then extract the unique authors.

python
def find_active_voices(query, min_followers=100, max_pages=10):
    seen, voices, cursor = set(), [], None
    for _ in range(max_pages):
        body = {"query": query, "order": "latest"}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(f"{BASE}/search-tweets", headers=POST_HEADERS,
                             json=body, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        for tw in data.get("tweets", []):
            u = tw.get("user", {})
            uid = u.get("id")
            if uid and uid not in seen and u.get("followers_count", 0) >= min_followers:
                seen.add(uid)
                voices.append({
                    "username": u.get("username"),
                    "followers": u.get("followers_count", 0),
                    "bio": u.get("description", ""),
                    "sample_tweet": (tw.get("full_text") or "")[:160],
                })
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return voices

voices = find_active_voices(
    '("need a CRM" OR "looking for a CRM") lang:en -filter:retweets')

The query string is where this technique earns its keep. A few patterns that surface intent:

GoalQuery
Buying intent for a category"need a [category]" OR "looking for [category]" lang:en -filter:retweets
Competitor frustration"[competitor]" (frustrated OR broken OR "switching from") -from:[competitor]
Migration intent"migrating from [tool]" OR "switching from [tool]" lang:en
Recommendation requests("any recommendation" OR "anyone use") [topic] lang:en
Pain-point discussion"struggling with" OR "how do you handle" [topic] lang:en

The full operator set, including the engagement filters that the official API silently drops, is in our Twitter search operators cheat sheet and the search operators docs.

Technique 5: Isolate the verified followers {#technique-5}

For PR lists, journalist outreach, investor research, or influencer vetting, the high-profile slice of an audience matters more than the raw count. /verified-followers returns only the verified accounts following a target, with the same shape and pagination as /followers.

python
def get_verified_followers(username, max_pages=10):
    verified, cursor = [], None
    for _ in range(max_pages):
        params = {"username": username}
        if cursor:
            params["next_cursor"] = cursor
        resp = requests.get(f"{BASE}/verified-followers", headers=AUTH,
                            params=params, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        verified.extend(data.get("users", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return verified

verified = get_verified_followers("openai")
verified.sort(key=lambda u: u.get("followers_count", 0), reverse=True)

Sorted by reach, this is a ready-made shortlist of the most influential accounts in a target's orbit. For finding and qualifying influential accounts beyond a single follower list, our influencer API for any niche covers the broader workflow, and the verified followers endpoint documents the response.

Technique 6: Capture the accounts that amplify content {#technique-6}

The people who retweet and quote-tweet content in your space are an audience that already acts, not just lurks. Two endpoints cover amplification. /retweeters returns the users who reposted a tweet. /quotes returns the full quote tweets, which include the quoting user plus their commentary, useful for sentiment-tagged outreach.

python
def get_retweeters(tweet_link, max_pages=10):
    users, cursor = [], None
    for _ in range(max_pages):
        body = {"tweet_link": tweet_link}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(f"{BASE}/retweeters", headers=POST_HEADERS,
                             json=body, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        users.extend(data.get("users", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return users

def get_quoters(tweet_link, max_pages=10):
    quotes, cursor = [], None
    for _ in range(max_pages):
        body = {"tweet_link": tweet_link}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(f"{BASE}/quotes", headers=POST_HEADERS,
                             json=body, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        quotes.extend(data.get("tweets", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return quotes

Quote tweets are tweet objects, not user objects: reach the quoting account through tweet["user"] and their commentary through tweet["full_text"]. The full engagement set (replies, quotes, retweeters) is covered in our Twitter Engagement API guide, with schemas in the retweeters and quote tweets references.

Combine the sources and score by overlap {#combine}

Each technique covers a different blind spot, so the strongest segment is the accounts that show up across more than one. Run several methods, deduplicate by user ID, and rank by how many sources each account appeared in:

python
def score_by_source(by_source):
    """by_source: {source_name: [user objects]} -> deduped users with source_count."""
    index = {}
    for source, users in by_source.items():
        for u in users:
            uid = u["id"]
            index.setdefault(uid, {"user": u, "sources": set()})["sources"].add(source)

    result = []
    for entry in index.values():
        u = entry["user"].copy()
        u["source_count"] = len(entry["sources"])
        u["sources"] = sorted(entry["sources"])
        result.append(u)

    result.sort(key=lambda r: (-r["source_count"], -r.get("followers_count", 0)))
    return result

combined = score_by_source({
    "followers_competitor_a": followers,
    "bio_ml_engineer": users,
    "intent_crm": voices,
    "community_indie_hackers": get_community_members("1966045657589813686"),
})

Accounts appearing in two or more sources are typically the highest-value part of the list. A person who follows your competitor, describes themselves with your target role, and tweeted intent this week is a stronger lead than any single signal alone.

Filter out bots and dead accounts {#filter}

Raw discovery output always carries noise: bots, abandoned accounts, and follow-farm profiles. Because every endpoint returns the full profile, one reusable filter cleans the whole list without extra calls:

python
from datetime import datetime, timezone, timedelta

def is_quality_account(user, min_followers=500, min_tweets=100, max_following_ratio=10):
    if user.get("protected", False):
        return False
    if user.get("followers_count", 0) < min_followers:
        return False
    if user.get("tweets_count", 0) < min_tweets:
        return False
    followers = user.get("followers_count", 1)
    if user.get("followings_count", 0) > followers * max_following_ratio:
        return False
    created = user.get("created_at")
    if created:
        try:
            dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
            if dt > datetime.now(timezone.utc) - timedelta(days=30):
                return False
        except ValueError:
            pass
    if not (user.get("description") or "").strip():
        return False
    return True

qualified = [u for u in combined if is_quality_account(u)]

A lopsided following-to-follower ratio, an empty bio, a brand-new account, and near-zero tweets are the cheapest bot tells. Tune the thresholds to your niche. For a deeper pass on scoring fake and inactive accounts, see our guide to auditing fake followers.

What does pulling an audience cost versus the official X API? {#cost}

Audience discovery is read-heavy by nature: thousands of profiles per project, often refreshed on a schedule. That is exactly the workload where the billing model decides the bill. The official X API charges per resource fetched, so every profile is a separate billable unit. A flat-rate Twitter/X API charges per request, and a follower request returns up to 200 profiles for the price of one call.

Official X API (pay-per-use)Sorsa API
Pricing modelPer resource fetchedPer request (flat)
10,000 follower profiles10,000 reads x $0.010 = ~$100~50 requests x $0.00199 (Pro) = ~$0.10
Profiles per request1 (billed each)up to 200 (/followers, billed as 1)
AuthenticationOAuth 2.0 + Bearer token, approved dev accountsingle ApiKey header, no approval
Rate limit~300 requests / 15 min, varies by endpoint20 requests/sec, every plan
PlansPay-per-use, 2M post-read monthly capStarter $49 / Pro $199 / Enterprise $899 per month

The gap compounds at scale. Pulling 10,000 competitor followers plus 10,000 bio-search profiles on the official API is roughly 20,000 user reads, about $200, before any filtering. The same two pulls on the Pro plan of a flat-rate Twitter API alternative land inside a single monthly request budget. Batching helps further: the optimizing API usage guide covers the page-size and batch patterns that keep request counts low. For the full provider-by-provider cost breakdown, see our Twitter API alternatives comparison. (Official X API figures reflect the per-resource pricing in effect after the April 2026 update; our own rates are flat per request on every plan.)

What to do with the audience once you have it {#what-next}

A list of accounts is raw material, not the outcome. Where the value actually shows up:

  • Outreach and lead generation. Filtered, deduplicated profiles become a prioritized contact list. The intent and overlap scores tell you who to reach first. Our lead generation on X workflow builds on exactly this output, and the practical guide to finding leads on X walks through the qualifying filters.
  • Content strategy. The bios, recurring phrases, and sample tweets you collected map the language your audience actually uses. Mirror their wording in hooks and threads instead of guessing.
  • Competitor and audience benchmarking. Compare the overlap between your followers and a rival's, or track how a competitor's audience grows over time, with competitor tracking on X. The developer-side method is in our Twitter competitor analysis guide.
  • Market sizing by geography. Aggregate the audience by country to see which markets are real before you commit a budget. The method is in our Twitter audience geography guide.

Whatever the destination, export is the same step. If you would rather skip code, the same profiles can go straight to a spreadsheet via our walkthrough on exporting X data to Google Sheets. The snippet below writes any technique's output to CSV:

python
import csv

def export_users_to_csv(users, output_file="audience.csv"):
    fields = ["user_id", "username", "display_name", "description",
              "followers_count", "followings_count", "tweets_count",
              "location", "verified", "created_at"]
    with open(output_file, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=fields)
        w.writeheader()
        for u in users:
            w.writerow({
                "user_id": u.get("id", ""),
                "username": u.get("username", ""),
                "display_name": u.get("display_name", ""),
                "description": (u.get("description") or "").replace("\n", " "),
                "followers_count": u.get("followers_count", 0),
                "followings_count": u.get("followings_count", 0),
                "tweets_count": u.get("tweets_count", 0),
                "location": u.get("location", ""),
                "verified": u.get("verified", False),
                "created_at": u.get("created_at", ""),
            })

In practice: a B2B SaaS team's audience build {#in-practice}

A roughly 12-person B2B SaaS marketing team came to this approach after running outreach by hand for two quarters. Their process was a researcher copying handles out of Advanced Search into a spreadsheet, which capped out around a few hundred accounts a week and produced no qualifying data. They rebuilt it as a pipeline: extract the followers of their three closest competitors, run a bio search for the two job titles their buyers hold, search tweets for migration intent against an incumbent tool, then combine and score by overlap. The accounts that hit two or more sources became a ranked outreach list of a few thousand qualified profiles, refreshed weekly.

The part that surprised them was the cost line. Their original plan to run this volume on the official X API priced the read workload at hundreds of dollars a month before filtering. Moving the data layer to a flat-rate Twitter scraper put the same pulls inside a sub-$200 monthly plan, a cut on the order of 30 to 50 times for read-heavy work, which is the realistic gap between per-resource billing and a flat per-request model for this kind of job.

How to try this yourself {#getting-started}

You can test every technique here without writing code first. The no-code Sorsa Playground runs the same endpoints through a UI: pick a method, set the page count, see the request cost upfront, and download the results. When you are ready to script it, the quickstart gets you a working request in a few minutes.

Setup is a single ApiKey header, with no developer-account review and no OAuth flow. The Starter plan is $49 for 10,000 requests, and every plan runs at a flat 20 requests per second, which on a 200-profile follower call is up to 4,000 profiles per second. For larger or scheduled audience builds, the pricing page has the per-request math and a calculator.

FAQ {#faq}

How do you find your target audience on Twitter (X)? Finding your target audience on X means collecting the specific accounts worth reaching as a structured list rather than scrolling the app. The practical methods are bio and tweet keyword search, extracting a competitor's followers, scraping a Community's members, and gathering the accounts that retweet relevant posts. An API returns these as full profiles you can filter and export.

Can you find Twitter users by a keyword in their bio? Yes. Searching bios, display names, and handles for a keyword surfaces accounts that self-identify with a role or niche, such as "Solidity developer" or "growth marketer." A user-search endpoint does this and returns each match as a full profile, including follower count and account age, so you can qualify candidates in code without a second lookup.

How do you get a list of a competitor's followers? A competitor's followers are accessible through a followers endpoint that returns the public follower list as full profiles, up to 200 per request, so a 10,000-follower list takes about 50 calls. Pulling several competitors and keeping the accounts that follow two or more produces a sharper, category-interested segment than any single account.

Is there an X API alternative with better rate limits for audience pulls? Yes. Sorsa API is a Twitter/X API alternative built for read-heavy work, with a flat 20 requests per second on every plan and no per-endpoint windows. It bills per request rather than per resource, so a single follower call returns up to 200 profiles for one unit of quota, and setup is a single API key with no approval queue.

What does it cost to pull 10,000 follower profiles? On the official X API, 10,000 follower profiles are billed as 10,000 resource reads at $0.010 each, roughly $100. On a flat per-request model, the same 10,000 profiles take about 50 requests at $0.002 each, around ten cents, because a followers endpoint returns up to 200 profiles per request.

Can you find an audience on X without coding? Yes. The Sorsa Playground runs the discovery endpoints through a web interface: choose a method such as user search or follower extraction, set how many pages to pull, see the request cost before running, and export the results. It uses the same data as the API, so you can validate an approach by hand before scripting it.


Reviewed by Keksich, founder of Sorsa, marketer and X API researcher.

How this guide was put together: the six methods are the ones we run against the live X data layer while building and operating Sorsa's alternative Twitter/X API, and every endpoint, parameter, and per-request behavior here was re-checked against the Sorsa API documentation during this revision. The cost comparison uses the official X API's per-resource pricing as published after its April 2026 update, cross-checked against our own published rates. Verified June 11, 2026.