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.
-
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.
-
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.
-
Posting frequency. Posts per week, counted over the same four weeks for every rival.
-
Content mix. The share of replies, quotes and posts with a photo or video.
-
Share of voice. A brand's mentions in a week divided by the mentions of every brand in the set.
-
Audience overlap. The share of a rival's followers who also follow you, measured against the smaller of the two lists.
-
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 matters | Sorsa API | Seat-based dashboard | Official X API |
|---|---|---|---|
| History | Search back to 2006; full timelines, no 3,200-post cap | From the day you sign up | Timelines stop at the latest 3,200 posts; archive search billed per post |
| Rivals | No cap | Capped by plan | No cap |
| Price | Profiles from $0.014 per 1,000; posts from $0.07 per 1,000 | Per seat per month | $10.00 per 1,000 profiles; $5.00 per 1,000 posts |
| Access | One API key | A login per seat | Developer account, OAuth, 3M post reads a month at most |
| Output | JSON for your sheets, dashboards and agents | The vendor's charts and exports | JSON |
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.
-
Get a key. Sign up and copy your API key from the dashboard. The first 100 requests are free and never expire.
-
Snapshot the whole set. Send every handle to
GET /info-batch(opens in a new tab), up to 100 per request, repeating theusernamesparameter.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" } ] } -
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, } -
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. Addmin_likeswhen 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()} -
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)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.
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:
| Column | Where it comes from | Formula |
|---|---|---|
followers | followers_count, GET /info-batch | As returned |
growth_pct | Two weekly rows | (this week − last week) ÷ last week × 100 |
posts_wk | created_at, POST /user-tweets | Own posts in 28 days ÷ 4 |
median_er | likes_count, retweet_count, reply_count, quote_count | Median of (sum ÷ followers × 100) per post |
reply_pct | is_reply | Replies ÷ all posts in the window × 100 |
media_pct | entities[].type is photo or video | Media posts ÷ own posts × 100 |
sov_pct | POST /mentions | Brand 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[].typeisphotoorvideo- 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).
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.
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.
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.
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_partnershipposts 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.
Sources
- Sorsa API reference: User Profile (Batch) (opens in a new tab)
- Sorsa API reference: User Tweets (opens in a new tab)
- Sorsa API reference: Search Mentions (opens in a new tab)
- Sorsa API docs: Competitor Analysis (opens in a new tab)
- X API pricing (opens in a new tab)
- X API: Timelines introduction (opens in a new tab)
- X Developers: X API PPU expansions update (opens in a new tab)