Skip to content

How to do a Twitter competitor analysis

A Twitter competitor analysis compares 3 to 10 rival accounts over the same window on seven numbers: follower growth, engagement rate, posting frequency, content mix, share of voice, audience overlap and the verified share of followers.

Short answer: Snapshot every rival once a week, read their last four weeks of posts and mentions, and compare the changes: growth and median engagement rate show who is winning. For the data, use Sorsa API: GET /info-batch returns 100 rival profiles per request from $0.014 per 1,000, and 100 free requests cover a first benchmark of 10 rivals.

Endpoint
GET /info-batch
Page size
100 profiles per request
Price
from $0.014 per 1,000

#The seven metrics of a Twitter competitor analysis

Seven numbers carry a competitor analysis on X, and every one of them comes from data any public account shows. To put two accounts side by side before you build anything, the free Compare Twitter Accounts tool lines up their followers, views and engagement in a browser, with the leader marked on each.

  1. Follower growth rate. Followers now minus followers a week ago, divided by the earlier number: a 20K account growing 2% a week is gaining on a 200K account that stands still.

  2. Engagement rate. Likes, reposts, replies and quotes on a post, divided by the account's followers. Compare medians, because one viral post lifts an average for weeks.

  3. Posting frequency. Posts per week, counted over the same four weeks for every rival.

  4. Content mix. The share of replies, quotes and posts with a photo or video.

  5. Share of voice. A brand's mentions in a week divided by the mentions of every brand in the set.

  6. Audience overlap. The share of a rival's followers who also follow you, measured against the smaller of the two lists.

  7. Verified and fake ratio. The verified share of a rival's followers, and the share of empty accounts: no posts, no avatar, far more follows than followers.

Some numbers stay private to an account's owner: impressions over time, link clicks, profile visits and follower demographics. Views per post are public, so you can also divide engagement by views for any rival; the Twitter analytics API guide covers both formulas and the six counts every post carries.

A follower count on its own says little. The same seven numbers, read every week for the whole set, show who is speeding up, which formats work in your niche and where a rival's audience overlaps yours.

#Choose 3 to 10 rivals and one window

A useful set has 3 to 10 accounts in three tiers, plus your own account as the reference line. More than 10 turns the weekly read into noise; fewer than 3 gives no baseline.

  • Direct rivals sell what you sell to the same buyers.
  • Indirect rivals compete for the same attention: a media account, a creator or a tool your buyers use instead.
  • Aspirational accounts sit where you want to be in a year, so the gap is measured, not guessed.

Three places reveal the rivals you don't know yet. Accounts that people mention in the same posts as you are usually competitors. A search of profile bios for your category words, through POST /search-users (opens in a new tab), lists the rest. And accounts your followers also follow compete for the same feed; the free Twitter Audience Overlap Checker measures that for any two accounts.

Fix one window for everyone: the last 28 days, ending on the same day for every account. A launch week inside a two-week window reads as a rival's normal pace; four weeks smooth it out.

#Dashboard tool or API: where the numbers come from

A seat-based dashboard starts counting on the day you connect it, charges for each seat and caps how many rivals you follow. An API used as the data layer backfills a rival's history, charges per request with no cap on rivals and returns JSON for your own sheet, dashboard or agent.

What mattersSorsa APISeat-based dashboardOfficial X API
HistorySearch back to 2006; full timelines, no 3,200-post capFrom the day you sign upTimelines stop at the latest 3,200 posts; archive search billed per post
RivalsNo capCapped by planNo cap
PriceProfiles from $0.014 per 1,000; posts from $0.07 per 1,000Per seat per month$10.00 per 1,000 profiles; $5.00 per 1,000 posts
AccessOne API keyA login per seatDeveloper account, OAuth, 3M post reads a month at most
OutputJSON for your sheets, dashboards and agentsThe vendor's charts and exportsJSON
  • History

    Sorsa API
    Search back to 2006; full timelines, no 3,200-post cap
    Seat-based dashboard
    From the day you sign up
    Official X API
    Timelines stop at the latest 3,200 posts; archive search billed per post
  • Rivals

    Sorsa API
    No cap
    Seat-based dashboard
    Capped by plan
    Official X API
    No cap
  • Price

    Sorsa API
    Profiles from $0.014 per 1,000; posts from $0.07 per 1,000
    Seat-based dashboard
    Per seat per month
    Official X API
    $10.00 per 1,000 profiles; $5.00 per 1,000 posts
  • Access

    Sorsa API
    One API key
    Seat-based dashboard
    A login per seat
    Official X API
    Developer account, OAuth, 3M post reads a month at most
  • Output

    Sorsa API
    JSON for your sheets, dashboards and agents
    Seat-based dashboard
    The vendor's charts and exports
    Official X API
    JSON

The price row is the one that grows with your set. The official X API bills every post and profile it returns, at the rates on its pricing page (opens in a new tab), while a Sorsa API request carries up to 100 profiles or 20 posts.

#Run the analysis with Sorsa API in five steps

To pull the numbers for a competitor analysis, use Sorsa API, a Twitter (X) API and scraper: one request returns up to 100 rival profiles, and posts come 20 per request back to 2006. Every plan runs at 20 requests per second on one API key, with no developer account or OAuth. The five steps below build the weekly run end to end; the competitor analysis workflow (opens in a new tab) in the docs carries the same job as one script.

  1. Get a key. Sign up and copy your API key from the dashboard. The first 100 requests are free and never expire.

  2. Snapshot the whole set. Send every handle to GET /info-batch (opens in a new tab), up to 100 per request, repeating the usernames parameter.

    Terminal
    curl -G "https://api.sorsa.io/v3/info-batch" \
      -H "ApiKey: YOUR_API_KEY" \
      --data-urlencode "usernames=northwind_roastery" \
      --data-urlencode "usernames=harbor_bean_supply" \
      --data-urlencode "usernames=kestrel_coffee_lab"
    
    Response200 OK
    {
      "users": [
        {
          "id": "1638204417762983936",
          "username": "northwind_roastery",
          "display_name": "Northwind Roastery",
          "description": "Small-batch coffee, roasted every Monday.",
          "bio_urls": ["https://example.com/subscribe"],
          "followers_count": 48210,
          "followings_count": 612,
          "tweets_count": 9314,
          "media_count": 2207,
          "verified": true,
          "pinned_tweet_ids": ["1851130250081816914"],
          "created_at": "2023-03-15T09:12:44Z"
        },
        {
          "id": "1702553980148703232",
          "username": "harbor_bean_supply",
          "display_name": "Harbor Bean Supply",
          "description": "Green coffee for roasters. Ships worldwide.",
          "bio_urls": [],
          "followers_count": 31904,
          "followings_count": 1480,
          "tweets_count": 5120,
          "media_count": 860,
          "verified": false,
          "pinned_tweet_ids": [],
          "created_at": "2023-09-14T16:40:03Z"
        }
      ]
    }
    
  3. Read each rival's last four weeks. Page through POST /user-tweets (opens in a new tab), 20 posts per request, until a whole page is older than the window. Every post carries its likes, reposts, replies, quotes, views and bookmarks, so the metrics need no further calls.

    import statistics
    from datetime import datetime, timedelta, timezone
    
    import requests
    
    BASE = "https://api.sorsa.io/v3"
    HEADERS = {"ApiKey": "YOUR_API_KEY"}
    WEEKS = 4
    
    
    def posted(t):
        return datetime.fromisoformat(t["created_at"].replace("Z", "+00:00"))
    
    
    def recent_posts(handle):
        since = datetime.now(timezone.utc) - timedelta(weeks=WEEKS)
        body, posts = {"username": handle, "with_replies": True}, []
        while True:
            r = requests.post(f"{BASE}/user-tweets", headers=HEADERS,
                              json=body, timeout=30)
            r.raise_for_status()
            page = r.json()
            fresh = [t for t in page.get("tweets", []) if posted(t) >= since]
            posts += fresh
            if not fresh or not page.get("next_cursor"):
                return posts
            body["next_cursor"] = page["next_cursor"]
    
    
    def metrics(handle, followers):
        posts = recent_posts(handle)
        own = [t for t in posts
               if not t["is_reply"] and not t.get("retweeted_status")]
        rates = [100 * (t["likes_count"] + t["retweet_count"]
                        + t["reply_count"] + t["quote_count"]) / followers
                 for t in own]
        media = sum(any(e["type"] in ("photo", "video")
                        for e in t.get("entities") or []) for t in own)
        replies = sum(t["is_reply"] for t in posts)
        return {
            "posts_wk": round(len(own) / WEEKS, 1),
            "median_er": round(statistics.median(rates), 3) if rates else 0,
            "reply_pct": round(100 * replies / len(posts), 1) if posts else 0,
            "media_pct": round(100 * media / len(own), 1) if own else 0,
        }
    
  4. Count each brand's share of voice. Ask POST /mentions (opens in a new tab) for every handle over the same week, 20 mentions per request, and divide each count by the total. Add min_likes when a brand gets thousands of mentions and you only want the ones people reacted to.

    Python
    def mentions(handle, since, until):
        body = {"query": handle, "since_date": since, "until_date": until}
        count = 0
        while True:
            r = requests.post(f"{BASE}/mentions", headers=HEADERS,
                              json=body, timeout=30)
            r.raise_for_status()
            page = r.json()
            count += len(page.get("tweets", []))
            if not page.get("next_cursor"):
                return count
            body["next_cursor"] = page["next_cursor"]
    
    
    brands = ["northwind_roastery", "harbor_bean_supply", "kestrel_coffee_lab"]
    week = {b: mentions(b, "2026-10-26", "2026-11-02") for b in brands}
    total = sum(week.values()) or 1
    sov = {b: round(100 * n / total, 1) for b, n in week.items()}
    
  5. Save one row per rival and compare it with last week. Append the rows to a CSV file on a weekly schedule (cron, a GitHub Actions workflow, any task runner); growth is the difference between this week's follower count and last week's.

    Python
    import csv
    from datetime import date
    from pathlib import Path
    
    PATH = "competitors.csv"
    FIELDS = ["week", "handle", "followers", "growth_pct", "posts_wk",
              "median_er", "reply_pct", "media_pct", "sov_pct"]
    
    
    def last_followers():
        if not Path(PATH).exists():
            return {}
        with open(PATH, newline="", encoding="utf-8") as f:
            return {r["handle"]: int(r["followers"]) for r in csv.DictReader(f)}
    
    
    profiles = requests.get(f"{BASE}/info-batch", headers=HEADERS,
                            params={"usernames": brands}, timeout=30).json()
    last, rows = last_followers(), []
    for u in profiles["users"]:
        h, f = u["username"], u["followers_count"]
        prev = last.get(h) or f
        rows.append({"week": date.today().isoformat(), "handle": h,
                     "followers": f,
                     "growth_pct": round(100 * (f - prev) / prev, 2),
                     "sov_pct": sov.get(h, 0), **metrics(h, f)})
    
    new_file = not Path(PATH).exists()
    with open(PATH, "a", newline="", encoding="utf-8") as f:
        out = csv.DictWriter(f, fieldnames=FIELDS)
        if new_file:
            out.writeheader()
        out.writerows(rows)
    
    The weekly competitor analysis run with Sorsa API Every week one script one API key a few seconds at 20 requests a second GET /info-batch up to 100 rivals per request followers, bio, pinned post POST /user-tweets 20 posts per request engagement, cadence, content mix POST /mentions 20 mentions per request share of voice One row per rival account growth Rival A +1.8% Rival B +0.4% Rival C -0.3% You +2.6% vs last week's row
    Three endpoints fill the whole sheet. Growth comes from the difference between two weekly rows, so the first week sets the baseline.

A 403 response means the key is missing or out of requests; a 429 means more than 20 requests in one second, so pause and retry. Failed requests are never charged.

#A Twitter competitor analysis template that fills itself

The template is one sheet with one row per rival per week, and every column maps to a field the API returns. Paste the header row into Google Sheets or Excel, or let the script from step 5 write the file for you.

competitors.csv
week,handle,followers,growth_pct,posts_wk,median_er,reply_pct,media_pct,sov_pct

Each column, the field it comes from and the formula:

ColumnWhere it comes fromFormula
followersfollowers_count, GET /info-batchAs returned
growth_pctTwo weekly rows(this week − last week) ÷ last week × 100
posts_wkcreated_at, POST /user-tweetsOwn posts in 28 days ÷ 4
median_erlikes_count, retweet_count, reply_count, quote_countMedian of (sum ÷ followers × 100) per post
reply_pctis_replyReplies ÷ all posts in the window × 100
media_pctentities[].type is photo or videoMedia posts ÷ own posts × 100
sov_pctPOST /mentionsBrand mentions ÷ mentions of the whole set × 100
  • followers

    Where it comes from
    followers_count, GET /info-batch
    Formula
    As returned
  • growth_pct

    Where it comes from
    Two weekly rows
    Formula
    (this week − last week) ÷ last week × 100
  • posts_wk

    Where it comes from
    created_at, POST /user-tweets
    Formula
    Own posts in 28 days ÷ 4
  • median_er

    Where it comes from
    likes_count, retweet_count, reply_count, quote_count
    Formula
    Median of (sum ÷ followers × 100) per post
  • reply_pct

    Where it comes from
    is_reply
    Formula
    Replies ÷ all posts in the window × 100
  • media_pct

    Where it comes from
    entities[].type is photo or video
    Formula
    Media posts ÷ own posts × 100
  • sov_pct

    Where it comes from
    POST /mentions
    Formula
    Brand mentions ÷ mentions of the whole set × 100

Add columns as the analysis grows: views from view_count for engagement by views, bio from description to spot a new positioning line, verified_share from the audience audit below. A worked example: if your median engagement rate is 0.41% and a rival's is 1.2% on the same follower count, sort the rival's posts by engagement and read the top ten before you change anything.

#Look back before you started tracking

A competitor's past is searchable: Sorsa API reads a rival's posts back to 2006, so a benchmark can start with last year's data on the day you set it up. Dashboards count from the day you connect them, and the official X API's user timeline returns only the latest 3,200 posts, per docs.x.com (opens in a new tab).

How far back each route can read a rival's posts today Sorsa API search from:, since:, until: back to 2006 Sorsa API timeline no 3,200-post cap every post since the account's first Official X API timeline latest 3,200 posts about 11 months at 10 posts a day Dashboard tool from your signup date only since you connected it 2006 2010 2014 2018 2022 2026
A rival's history is there before you start tracking it. Only an API that reads back to 2006 can backfill it.

To study how a rival ran last year's holiday season, send X's own operators to POST /search-tweets (opens in a new tab): from: picks the account, since: and until: set the dates, and each request returns about 20 posts with their counts.

Terminal
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "from:northwind_roastery since:2025-11-01 until:2026-01-01",
       "order": "latest"}'

The same query with a brand name instead of from: returns what people said about the rival in that window, which is a share of voice you can measure for any past month.

#Four signals a dashboard rarely shows

Beyond the seven metrics, four public signals show what a rival is doing before it shows in their numbers.

Sponsored posts. Every post Sorsa API returns carries paid_partnership, the flag X shows on paid partnerships. Search the posts that tag a rival and keep the flagged ones: the authors are the creators the rival pays.

Python
body = {"query": "@northwind_roastery", "order": "latest"}
r = requests.post(f"{BASE}/search-tweets", headers=HEADERS, json=body)
paid = [t for t in r.json()["tweets"] if t.get("paid_partnership")]
print({t["user"]["username"] for t in paid})

Where their audience lives. GET /about (opens in a new tab) returns the country X assigns to an account. Run it on a sample of 1,000 of a rival's followers and you see which markets they reach; the Twitter location API guide shows how close a sample comes to the full list.

Quiet strategy moves. A new bio line, a new link in bio_urls or a new pinned post often comes before an announcement. Compare them week to week in the /info-batch snapshot, and page GET /follows (opens in a new tab) once a month: new follows of partners, hires or investors are public.

Audience quality. GET /verified-followers (opens in a new tab) lists only the verified accounts following a rival, and GET /followers (opens in a new tab) returns 200 followers per request for the overlap and fake-follower checks. The Twitter followers API guide covers paging, overlap by ID and the signals of empty accounts.

#What tracking competitors costs per month

Tracking 10 rivals every week costs from $0.68 a month in Sorsa API requests ($0.97 at the Pro rate) against $143.77 on the official X API. The example reads 100 posts and 100 mentions per rival each week and samples 1,000 followers per rival each month; change the numbers to price your own set.

Rivals you track
Posts read per rival, each week
Mentions read per rival, each week
Audience sample per rival, each month
Official X API, a month
$143.77
$0.005 per post, $0.010 per profile or follower
Sorsa API, a month
from $0.68
$0.97 at the Pro rate

The whole job uses 5% of the smallest plan's monthly requests.

One profile request for the whole set, posts and mentions 20 per request, followers 200 per request; the official X API bills each post at $0.005 and each profile or follower at $0.010.

The gap comes from the unit of billing. The official X API charges for every post and every profile it returns, and it needs a developer account and OAuth. It stops at 3M post reads a month; above that only an Enterprise contract remains, from about $42,000 a month. From November 22, 2026 it also bills objects returned through expansions at the standard rate, per X's notice of October 23 (opens in a new tab). If you expand each mention's author at $0.010, the example above rises to $187.10.

For tracking competitors on X, choose Sorsa API: rival profiles cost from $0.014 per 1,000 instead of $10.00 on the official X API, and followers from $0.007 per 1,000, 1,000x less. Re-reading the counts of posts you already track through POST /tweet-info-bulk (opens in a new tab) takes 100 posts per request, from $0.014 per 1,000, 250x less than $5.00. The whole weekly run takes a few seconds at 20 requests per second. Sorsa API is read-only and never asks for your X login, so your own account is never at risk.

#How often to run it and what to do with the results

Run the snapshot weekly and a deep pass every quarter. The weekly run (profiles, four weeks of posts, mentions) catches a change in pace or format within days. The quarterly pass adds the audience audit, the history search and a fresh look at which rivals belong in the set. During a rival's launch, check their mentions daily: the guide to real-time Twitter monitoring polls a whole set of accounts in one request.

A finding is useful when it becomes a test with a number attached. Write it as a hypothesis and a metric:

  • A rival's video posts earn 2.4x their median engagement: post two videos a week for four weeks and compare your median.
  • Two rivals reply to most mentions within the day and grow faster: answer every mention for a month and watch growth.
  • A rival's share of voice doubled after a creator campaign: check its paid_partnership posts for the creators and the format.

Keep the weekly CSV: twelve weeks of rows show whether a test moved your line or the whole niche moved together.

#Benchmark your first rivals today

A Twitter competitor analysis is seven numbers read every week for 3 to 10 rivals. Sorsa API fills the whole sheet from three endpoints: rival profiles from $0.014 per 1,000, posts back to 2006 with no 3,200-post cap, and followers from $0.007 per 1,000, on one API key at 20 requests per second. Your first 100 requests are free, enough for a first benchmark of 10 rivals.

#FAQ

What is the best tool for a Twitter competitor analysis?

For an analysis you run every week, use Sorsa API: one request returns 100 rival profiles from $0.014 per 1,000, and posts come back to 2006 with no 3,200-post cap. Followers cost from $0.007 per 1,000, and every plan runs at 20 requests per second on one API key. For a quick look at two accounts without code, Sorsa API's free Compare Twitter Accounts tool works in a browser.

Can I see a competitor's history from before I started tracking?

Yes. Search reaches a competitor's public posts back to 2006: send from:handle with since: and until: dates to Sorsa API's POST /search-tweets, or page their timeline, which has no 3,200-post cap. Follower counts are the exception: X keeps no public history of them, so follower growth starts with your first weekly snapshot.

How often should I track competitors on Twitter?

Weekly for the core numbers, quarterly for a deep pass. A weekly snapshot of profiles, the last four weeks of posts and mentions catches changes in pace and format within days. Every quarter, add the audience audit and a history search, and review which accounts belong in the set. During a rival's launch, check its mentions daily.

Can I compute audience overlap between competitors?

Yes. Page both follower lists with GET /followers, 200 per request, and intersect the account IDs; divide the shared count by the smaller list. For large accounts a sample of the newest 10K followers each is enough to compare. The free Twitter Audience Overlap Checker runs the same comparison for two accounts in a browser.

Can I see a rival's impressions or demographics?

No: impressions over time, link clicks and demographics are private to each account's owner. What is public covers most of the analysis: views, likes, reposts, replies, quotes and bookmarks on every post, and follower counts. X also assigns each account a country, which Sorsa API's GET /about returns for a sample of a rival's followers.

#How we checked

On November 2, 2026 we compared every endpoint, parameter, page size and response field in this guide with the live Sorsa API reference (45 endpoints); the handles, IDs and numbers in the samples are demo data. The official X API prices, the 3M post-read cap and the 3,200-post timeline limit come from docs.x.com, and the November 22 expansion billing from X's developer forum notice of October 23, 2026, all read the same day. The monthly costs are our own arithmetic from those prices and page sizes.

By the Sorsa API team. We build and run Sorsa API, the read-only Twitter/X API and scraper, in production since 2022 (formerly TweetScout API) with 6B+ requests served. About Sorsa API

What changed
  • Rewritten from scratch: the seven metrics, a five-step run with Sorsa API in Python and Node.js, a template that fills itself, history back to 2006, four extra signals (sponsored posts, audience country, quiet moves, audience quality) and a monthly cost planner; the competitor-tracking page moved here.
  • First published.