Updated July 2026: reworked the cost comparison around per-1,000-tweet rates, added the 100 free requests starting option, refreshed the official X API per-read pricing, and clarified which operators the v2 endpoint silently drops.
Key Takeaway
The Twitter Search API lets developers query the public X timeline programmatically with keyword and operator filters. In 2026 there are two practical paths: the official X API v2 recent-search endpoint, billed pay-per-use with a limited operator set, and third-party REST APIs that pass the full web operator set through on flat monthly plans starting with free requests.
For read-only search at scale, Sorsa API, an alternative Twitter/X API provider, is the option we recommend and the one we cover end to end here. Its /search-tweets endpoint passes the full web operator set through, including the min_faves:, min_retweets:, and min_replies: engagement filters the official v2 endpoint silently drops; it runs on a flat 20 requests per second on every plan with no per-endpoint windows; and it starts with 100 free requests (no card, all 40 endpoints) before moving to flat monthly plans that work out to as little as $0.02 per 1,000 tweets when batched, with no developer-account approval and a setup that takes minutes. The one thing it does not do is write: posting, liking, and DMs stay with the official API.
X's web search bar is fine when you are killing time. It is useless when you need to pull 50,000 tweets matching a complex Boolean query, run it on a schedule, push the results into a Postgres warehouse, and have someone in compliance audit the pipeline next quarter. That is what the Twitter Search API is for. This guide walks through how it works in 2026, the operators that actually fire, the code that handles real production traffic, and where each option fits.
We have been building against Twitter's search endpoints since the v1.1 era. Through the 2023 pricing overhaul, the v2 migration, and the 2026 shift to credit-based pay-per-use, the underlying mental model has not changed much: you send a query string, you get JSON back, you paginate with a cursor. What has changed is which provider you pay, how much you pay, and which operators silently get dropped before your query is executed. That last part trips up most people.
Table of Contents
- Why Search Tweets Programmatically?
- The Twitter Search API Landscape in 2026
- The
/search-tweetsEndpoint: Request Anatomy - What's in the Response
- Search Operators That Actually Work
- Two Operator Sets: Web Search vs the Official v2 API
- How Do You Search Tweets by Hashtag?
- Pagination: Collecting Thousands of Tweets
- Working Code: Python and JavaScript
- Real-World Query Templates
- Search Tweets vs. Track Mentions: Which Endpoint?
- How Sorsa Search Compares to the Official X API
- Common Errors and Troubleshooting
- Frequently Asked Questions
- Getting Started
Why Search Tweets Programmatically?
A search API exists because the use cases below cannot survive on manual browser refreshes:
Social listening and brand monitoring. Track every public mention of your product or competitors with structured JSON delivered into Slack, a dashboard, or an alerts pipeline. The signal is in the volume and the trend, not in any single tweet, which is the whole premise of social listening at scale.
Competitor and market intelligence. Pull engagement-filtered tweets from your competitors' accounts, identify which posts performed, and build a content benchmark for ongoing competitor tracking. Combine from:competitor min_faves:100 -filter:replies with date windows to compare quarter over quarter.
Sentiment analysis. Feed tweet text into a transformer model and score it. The search API gives you the raw text plus metrics (likes, replies, views) that double as confidence weights when aggregating sentiment. We covered the full pipeline in our Twitter sentiment analysis guide.
Lead generation. Searches like "looking for" (api OR tool) twitter surface people who are actively asking for what you sell. A well-crafted query is a free lead list, which is the basis of lead generation on X.
Academic and journalistic research. Academic research needs reproducible, auditable queries against a defined time window. A search endpoint with since: and until: is a primary data source, and the historical archive goes back to the first tweet in 2006.
Trend detection. Run the same query on a 5-minute cron, store the result counts, and detect spikes. This is how event-detection systems and crypto sentiment dashboards work under the hood, and it is the pattern behind real-time monitoring built on a polling loop.
The Twitter Search API Landscape in 2026
There is no single "Twitter Search API" anymore. There are three categories, and the spread between them on price and capability is wider than most developers realize.
The Official X API v2
Endpoint: /2/tweets/search/recent and /2/tweets/search/all. As of 2026, the X API runs on a credit-based pay-per-use model: each post read costs roughly $0.005, and each user (author) read is billed separately at roughly $0.010. There is no free tier and no free credit allowance, so you buy credits up front before any request goes through. Authentication uses OAuth 2.0 Bearer Tokens.
The harder problem is operator coverage. The official v2 endpoint accepts a much smaller subset of operators than the web search bar at x.com/search. Engagement filters that production teams rely on, including min_faves:, min_retweets:, min_replies:, and within_time:, are silently ignored if you include them in a v2 query. We have seen teams build full pipelines on min_faves: filters before discovering this, then have to refactor.
The official archive search is available but priced for enterprise budgets. For most read-only research and monitoring workloads, the math does not work out. If you want the full timeline of how X's pricing got here, our Twitter API pricing breakdown walks through every change.
Open-Source Scrapers
Twikit, TweeterPy, and XActions are still functional as of mid-2026. Twint is dead. twscrape and snscrape are broken in most setups. The maintained libraries work for small one-off jobs, but they sit on top of the public web search interface and break whenever X tweaks its frontend. None of them are realistic for production pipelines that need uptime guarantees.
Third-Party Search APIs
This is the category Sorsa sits in: services that run their own scraping infrastructure behind a clean REST API, expose the full web operator set, and charge predictable rates. There are other providers in this space, and if all you want is the absolute lowest per-call price even at the cost of reliability or completeness, one of those may fit a narrow case. For dependable, complete, read-only access at a fair flat rate, Sorsa is the one we build, operate, and recommend.
The reasoning for our /search-tweets endpoint over the alternatives comes down to four concrete things: a flat monthly plan that does not multiply credits by endpoint type, the full web operator set passed through without silent drops, the same 20 requests per second across every plan, and authentication that is one header (ApiKey: YOUR_KEY) with no OAuth flow. For an end-to-end walkthrough off the official API, including a broader survey of read-only options, see our Twitter API migration guide.
The /search-tweets Endpoint: Request Anatomy
Send a POST request to:
POST https://api.sorsa.io/v3/search-tweets
Authentication is one header: ApiKey: YOUR_API_KEY (case-sensitive). No Bearer tokens, no OAuth dance, no callback URLs to register.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search keywords. Supports the full set of native X search operators. |
order | string | No | "popular" (default) matches the "Top" tab in X search. "latest" returns chronological, newest first. |
next_cursor | string | No | Pagination cursor from a previous response. Omit on the first request. |
Minimal cURL Example
curl -X POST https://api.sorsa.io/v3/search-tweets \
-H "ApiKey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "artificial intelligence",
"order": "latest"
}'
Why POST and Not GET?
Search queries get long. A real-world brand monitoring query can easily run past 200 characters once you add Boolean groupings, exclusions, language filters, and engagement thresholds. Putting them in a URL means URL-encoding every operator and praying the upstream proxy does not truncate. Putting them in a JSON body avoids the URL length and encoding problem entirely. The official v2 search endpoint takes the opposite approach (a URL-encoded query parameter on a GET request), which is part of why long official-API queries hit the access-level character cap (512 characters on self-serve recent search) faster than people expect.
If you want to build queries visually before writing code, the Sorsa search builder renders the same operators as a form and outputs the query string you would paste into your script, and the interactive playground runs full requests against your key without writing any client code. Both are linked in the Getting Started section below.
What's in the Response
The endpoint returns a JSON object with two top-level fields: an array of tweet objects and a pagination cursor.
{
"tweets": [
{
"id": "2029914600217473314",
"full_text": "The latest breakthroughs in AI are reshaping automation.",
"created_at": "2026-03-06T13:38:49Z",
"lang": "en",
"likes_count": 142,
"retweet_count": 38,
"reply_count": 12,
"quote_count": 5,
"view_count": 28400,
"bookmark_count": 19,
"is_reply": false,
"is_quote_status": false,
"conversation_id_str": "2029914600217473314",
"entities": [],
"user": {
"id": "1422280682240450563",
"username": "tech_insider",
"display_name": "Tech Insider",
"description": "Breaking tech news and analysis.",
"followers_count": 84200,
"verified": true
}
}
],
"next_cursor": "DAABCgABGSmiaxkAAgoAAgjEJ..."
}
A few things matter here.
The full author profile ships inside every tweet. Across the migrations we have handled, the biggest dev-time saving is rarely the price drop. It is not having to do a separate users/by/ids lookup for each tweet author. The official v2 endpoint requires you to add an expansions=author_id parameter and then walk through an includes.users array to match author IDs back to tweets, and it bills each of those author reads separately. In this response, the user object is embedded directly and at no extra cost. One request, both content and authorship.
Engagement metrics are not optional fields. Likes, retweets, replies, quotes, views, and bookmarks are always present on every tweet. No tweet.fields=public_metrics to remember.
next_cursor is the only pagination signal you need. When the field is a string, more results are available. When it is null or missing, you have reached the end of the result set.
For the complete field reference on Tweet and User objects, see the response format reference in the API docs.
Search Operators That Actually Work
X's web search supports a large set of operators, and the high-leverage subset below covers roughly 90% of real workloads. We keep this list practical on purpose; for the full catalog, including geo operators, source filters, card filters, and the language-code edge cases, see our complete Twitter search operators cheat sheet.
Keywords and phrases
artificial intelligencematches tweets containing any of these words"artificial intelligence"matches the exact phrase- Word stemming is on by default:
bearwill also matchbears
User-based
from:elonmusktweets posted by an accountto:openaitweets replying to an account@sorsa_apptweets mentioning an account
Engagement filters
min_faves:100at least 100 likesmin_retweets:50at least 50 retweetsmin_replies:10at least 10 replies
These three are the operators most worth knowing exist. They are also the ones the official X API v2 silently drops, so if you migrate code from a different provider you may see your "low quality" filter stop working without an error.
Content filters
filter:media,filter:images,filter:videos,filter:links- Prefix with
-to exclude:-filter:retweets,-filter:replies,-filter:links
Language and date
lang:en(any ISO 639-1 code:es,fr,de,ja, and so on)since:2026-01-01on or after this dateuntil:2026-03-01before this date (exclusive)
Boolean logic
(bitcoin OR ethereum) min_faves:100 lang:enparentheses for groupscrypto -scam -airdropexclusion with a minus prefix
For the operator behavior reference, the community-maintained igorbrigadir/twitter-advanced-search repository on GitHub is the most thorough public source.
Two Operator Sets: Web Search vs the Official v2 API
There are two distinct operator sets on X, and confusing them is the single most common reason a query that "works" in one place returns nothing in another. Name the set you are targeting before you write the query.
Web search operators are what twitter.com/search, TweetDeck, and scraping-based REST APIs accept. This is the set in the section above, and it is what the Sorsa /search-tweets endpoint passes through unchanged.
Official X API v2 operators are a smaller subset with different syntax. Instead of filter:media and -filter:retweets, the v2 endpoint uses has:media, has:links, is:retweet, and is:reply. It adds place_country:US for geography and context: for topic and entity annotations, but it does not accept the engagement filters at all. If you are on the official endpoint, min_faves:, min_retweets:, min_replies:, within_time:, and filter:blue_verified are simply ignored, with no error returned.
A few operators are widely misunderstood or unreliable, on any provider, in 2026:
| Operator | What people get wrong |
|---|---|
filter:verified vs filter:blue_verified | filter:verified matches legacy verified accounts; filter:blue_verified matches paid X Premium accounts. For editorial signal you usually want the former, not the latter. |
within_time:7d | A rolling window measured from query time, so the same query returns a different set tomorrow. For reproducible datasets use explicit since: and until: dates instead. |
from:user vs @user | from:user returns tweets the account authored; @user returns any tweet mentioning it, including replies and quotes from others. |
near:, within:, geocode: | Exact-coordinate geotagging is largely deprecated, so geo operators now have reduced coverage and should not be relied on for completeness. |
Knowing which set you are on saves hours of debugging a query that is valid but quietly returning empty.
How Do You Search Tweets by Hashtag?
To search tweets by hashtag through an API, pass the hashtag as a standalone operator in your query, for example #worldcup. A hashtag works on its own and can be combined with engagement, language, and date filters to narrow the result set, which is how you turn a noisy hashtag into a usable dataset.
On the Sorsa /search-tweets endpoint, the body looks like this:
{
"query": "#worldcup min_faves:50 lang:en -filter:retweets since:2026-06-01",
"order": "latest"
}
That returns English-language, original (non-retweet) tweets carrying #worldcup with at least 50 likes, posted on or after June 1. Drop the engagement filter for raw volume, or raise it to surface only the posts that traveled.
The official X API v2 also matches #hashtag as a query term, but with two caveats that bite hashtag-tracking projects: the engagement floor (min_faves:) that keeps a popular hashtag from drowning you in noise is not available, and recent search is capped at roughly the last seven days unless you are on a higher-commit archive tier. If your goal is "every tweet with this hashtag above N engagement, going back months," the flat-rate web-operator path is the one that actually does it. The full set of hashtag-adjacent operators (filter:hashtags, cashtags, and the media-only language codes) is covered in the search operators cheat sheet linked earlier.
Pagination: Collecting Thousands of Tweets
A single search request returns one page of around 20 tweets. To pull larger datasets you use cursor-based pagination.
The logic is four steps:
- First request. Send query and order. Do not include
next_cursor. - Read the cursor. The response contains a
next_cursorstring. - Next request. Send the same query, same order, plus the
next_cursorvalue you just received. - Repeat until
next_cursorisnull, empty, or absent.
This is more reliable than offset-based pagination because new tweets posted between your requests do not cause duplicates or skipped results. The cursor encodes a position in the result set, not a numeric offset.
Date-Range Chunking for Deep Pagination
There is one edge case worth knowing about before you trust a single cursor for 100,000 tweets.
On a market-research pull of 200,000 tweets matching bitcoin lang:en, a single cursor loop started returning duplicates against earlier pages around page 70 and stopped advancing entirely around page 90. This is not specific to our API: any search index against a moving timeline has this property when you walk it deep enough.
The fix is to break the query into date-range chunks. Instead of one query over all time, run the same query for each week:
import datetime as dt
import time
import requests
API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"
def search_in_window(base_query, since, until, max_pages=50):
"""Paginate within a since/until window."""
full_query = f"{base_query} since:{since} until:{until}"
cursor = None
out = []
for _ in range(max_pages):
body = {"query": full_query, "order": "latest"}
if cursor:
body["next_cursor"] = cursor
resp = requests.post(
URL,
headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
json=body,
)
resp.raise_for_status()
data = resp.json()
out.extend(data.get("tweets", []))
cursor = data.get("next_cursor")
if not cursor:
break
time.sleep(0.1)
return out
def search_chunked(base_query, start, end, days_per_chunk=7):
"""Walk a date range in chunks."""
all_tweets = []
cursor_date = start
while cursor_date < end:
next_date = min(cursor_date + dt.timedelta(days=days_per_chunk), end)
chunk = search_in_window(
base_query,
cursor_date.strftime("%Y-%m-%d"),
next_date.strftime("%Y-%m-%d"),
)
all_tweets.extend(chunk)
print(f"{cursor_date} -> {next_date}: {len(chunk)} tweets")
cursor_date = next_date
return all_tweets
tweets = search_chunked(
"bitcoin lang:en",
dt.date(2026, 1, 1),
dt.date(2026, 4, 1),
days_per_chunk=7,
)
A weekly chunk on a noisy query like bitcoin lang:en typically yields a clean cursor walk to completion without dupe drift. For quieter queries you can stretch to monthly chunks. For very high-volume queries (think lang:en with no other filters) you may want daily.
For more on pagination patterns and rate-limit-aware retry logic, see our Twitter API rate limits guide.
Working Code: Python and JavaScript
The examples below are production patterns we run against the live endpoint: cursor pagination, 429 backoff with exponential retries, and a small batching gap to respect the 20 req/s ceiling.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"
def search_tweets(query, order="popular", max_pages=5, max_retries=3):
"""
Search tweets with cursor pagination and 429 backoff.
Args:
query: Search string (X operators supported).
order: "popular" or "latest".
max_pages: Maximum pages to fetch.
max_retries: Retries on 429 before giving up on a page.
Returns:
List of tweet dicts.
"""
all_tweets = []
next_cursor = None
for page in range(max_pages):
body = {"query": query, "order": order}
if next_cursor:
body["next_cursor"] = next_cursor
for attempt in range(max_retries):
resp = requests.post(
URL,
headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
json=body,
)
if resp.status_code == 429:
wait = 2 ** attempt
print(f"Rate limited. Sleeping {wait}s.")
time.sleep(wait)
continue
resp.raise_for_status()
break
else:
print(f"Page {page + 1} failed after {max_retries} retries.")
break
data = resp.json()
tweets = data.get("tweets", [])
all_tweets.extend(tweets)
print(f"Page {page + 1}: {len(tweets)} tweets (total {len(all_tweets)})")
next_cursor = data.get("next_cursor")
if not next_cursor:
print("End of results.")
break
time.sleep(0.1)
return all_tweets
# Usage
tweets = search_tweets('"Sorsa API" min_faves:5 lang:en', max_pages=10)
for t in tweets:
u = t["user"]
print(f"@{u['username']} ({u['followers_count']} followers)")
print(f" {t['full_text'][:120]}")
print(f" L:{t['likes_count']} RT:{t['retweet_count']} V:{t.get('view_count', 'N/A')}")
JavaScript (Node.js)
const API_KEY = "YOUR_API_KEY";
const URL = "https://api.sorsa.io/v3/search-tweets";
async function searchTweets(query, order = "popular", maxPages = 5, maxRetries = 3) {
const allTweets = [];
let nextCursor = null;
for (let page = 0; page < maxPages; page++) {
const body = { query, order };
if (nextCursor) body.next_cursor = nextCursor;
let data;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch(URL, {
method: "POST",
headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (resp.status === 429) {
const wait = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Sleeping ${wait}ms.`);
await new Promise((r) => setTimeout(r, wait));
continue;
}
if (!resp.ok) throw new Error(`API error: ${resp.status}`);
data = await resp.json();
break;
}
if (!data) break;
const tweets = data.tweets || [];
allTweets.push(...tweets);
console.log(`Page ${page + 1}: ${tweets.length} tweets (total ${allTweets.length})`);
nextCursor = data.next_cursor;
if (!nextCursor) break;
await new Promise((r) => setTimeout(r, 100));
}
return allTweets;
}
(async () => {
const tweets = await searchTweets("bitcoin lang:en min_faves:50", "latest", 5);
for (const t of tweets) {
console.log(`@${t.user.username}: ${t.full_text.slice(0, 100)}`);
}
})();
CSV Export Pipeline
A common downstream pattern is search to CSV, then load into a notebook or BI tool:
import requests, time, csv
API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"
def search_to_csv(query, order="popular", max_pages=10, out="tweets.csv"):
fields = [
"tweet_id", "created_at", "full_text", "lang",
"likes", "retweets", "replies", "quotes", "views",
"username", "display_name", "followers_count", "verified",
]
with open(out, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=fields)
w.writeheader()
cursor, total = None, 0
for _ in range(max_pages):
body = {"query": query, "order": order}
if cursor:
body["next_cursor"] = cursor
r = requests.post(
URL,
headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
json=body,
)
r.raise_for_status()
data = r.json()
for t in data.get("tweets", []):
u = t.get("user", {})
w.writerow({
"tweet_id": t["id"],
"created_at": t["created_at"],
"full_text": t["full_text"],
"lang": t.get("lang", ""),
"likes": t.get("likes_count", 0),
"retweets": t.get("retweet_count", 0),
"replies": t.get("reply_count", 0),
"quotes": t.get("quote_count", 0),
"views": t.get("view_count", 0),
"username": u.get("username", ""),
"display_name": u.get("display_name", ""),
"followers_count": u.get("followers_count", 0),
"verified": u.get("verified", False),
})
total += 1
cursor = data.get("next_cursor")
if not cursor:
break
time.sleep(0.1)
print(f"Exported {total} tweets to {out}")
search_to_csv(
'(bitcoin OR ethereum) lang:en min_faves:10 -filter:retweets',
order="latest",
max_pages=20,
out="crypto_tweets.csv",
)
At ~20 tweets per page, max_pages=50 gets you ~1,000 tweets. Combined with date-range chunking, you can scale this to six or seven figures without rewriting the loop, which is exactly how you assemble a Twitter dataset for machine learning.
Real-World Query Templates
Copy these, swap the variables, ship them.
Brand monitoring (organic mentions only)
("yourbrand" OR "@yourbrand") -from:yourbrand -filter:retweets lang:en
Catches what people say about you, excludes your own posts and retweets, English only. Run on a 5-minute cron, pipe into Slack.
Competitor content benchmark
(from:competitor1 OR from:competitor2 OR from:competitor3) min_faves:100 -filter:replies since:2026-01-01
Their top-performing original posts in the date window. Drop into a spreadsheet, sort by engagement, learn what works. Our Twitter competitor analysis guide builds this into a repeatable workflow.
Sentiment tracking
(bitcoin OR $BTC) (bullish OR bearish OR moon OR crash OR pump OR dump) min_faves:20 lang:en
Sentiment-laden tweets above a quality floor. Pair with the sentiment analysis pipeline linked above for scoring.
Product feedback mining
"yourproduct" (bug OR broken OR issue OR love OR amazing OR hate) -filter:retweets
Organic feedback, both flavors. Useful for support and product roadmap input.
Lead generation
("looking for" OR "anyone recommend" OR "best tool for") (api OR scraping OR twitter data) -filter:retweets lang:en
People actively asking. Filter further by min_faves:1 to drop bot traffic.
Event reaction window
"product launch" OR "announcement" from:yourbrand since:2026-05-01 until:2026-05-08
Reactions to your own launch within a defined window. Pair from:yourbrand (your posts) with a separate query for the surrounding conversation.
Search Tweets vs. Track Mentions: Which Endpoint?
Two endpoints overlap on mention-tracking workloads. Which to pick:
/search-tweets is the general-purpose endpoint. Any operator combination, any query shape. Use it when you need flexibility, when your query is not just about one handle, or when you want to mix mentions with engagement filters in a single Boolean expression.
/mentions is purpose-built for tracking @-mentions of a single handle. It exposes the richest filter set in our API: min_likes, min_replies, min_retweets, since_date, until_date, all as first-class parameters instead of inline operators. Use it when your workflow is specifically "alert me on new mentions of @brand above X engagement."
Quick decision rule: if your query starts with @handle and ends with engagement filters, use /mentions. If it involves multiple terms, Boolean groups, or non-mention operators, use /search-tweets.
For a closer look at the mentions endpoint and brand-monitoring workflows, see our Twitter mentions API guide.
How Sorsa Search Compares to the Official X API
Sorsa is our product, so here is the honest side-by-side with real numbers for both, including where the official API still wins. For pure read-only search, the operator silent-drop problem and the per-read author billing are the two practical reasons teams move off v2.
| Dimension | Sorsa /search-tweets | Official X API v2 /2/tweets/search/recent |
|---|---|---|
| Pricing model | Flat monthly plans; from $0.02 per 1,000 tweets when batched | Pay-per-use, ~$0.005 per post read (about $5.00 per 1,000) |
| Free to start | 100 free requests, no card, all endpoints | No free credit; buy credits before the first call |
| Author profile in response | Embedded by default, no extra charge | Separate user read, ~$0.010 each, via expansions=author_id |
| Auth | API key in ApiKey header | OAuth 2.0 Bearer Token |
| Account approval | Instant signup, no approval queue | Developer Console signup |
Engagement operators (min_faves:, min_retweets:, min_replies:) | Yes | Silently ignored |
| Web operator parity | Full set passed through | Subset only, different syntax (has:, is:) |
| Historical archive | Back to 2006 | ~7 days recent; full archive on higher-commit tiers only |
| Rate limit | 20 req/s, all plans | Credit-based, varies |
| Real-time monitoring | Polling at 20 req/s | Filtered stream available |
| Write actions (post, like, DM) | None (read-only) | Yes |
On read cost the gap is large. Reading 1,000 posts on the official X API runs about $5.00, and that is before the separate $0.010-per-author charge. On Sorsa the same 1,000 tweets cost about $0.10 through the paginated /search-tweets endpoint (roughly 20 tweets per request) and drop to about $0.02 when the same IDs are pulled through the /tweet-info-bulk batch endpoint, author profiles included either way. On the batched base that is up to 50x cheaper per 1,000 tweets, with the author data that X bills as a second read included at no extra cost.
Where the official API is the right call: filtered stream for true push-based real-time, and write actions (posting, liking, following) that we do not offer at all. For everything read-only, the flat-rate path is both cheaper and simpler to operate at any meaningful volume.
In practice. A roughly 12-person social-analytics team we worked with sat in the awkward middle zone: they were pulling somewhere between 50,000 and a few million post reads a month, well past the point where pay-per-use stays cheap, but nowhere near the volume that justifies an enterprise contract. On the official API, author profiles doubled their per-search cost because every tweet's author was billed as a separate read. Moving the read workload to a flat plan with author data embedded cut their monthly data bill by more than an order of magnitude and made the number predictable, which mattered more to their finance team than the raw saving. The migration that drove the change is the same one mapped step by step in the migration guide linked earlier.
Common Errors and Troubleshooting
Things that bite people, in rough order of how often we see them in support.
Empty results on a query you know should return something. Usual suspects: a typo in an operator (min_likes instead of min_faves), a phrase that needs double quotes ("black cat" not black cat), or a -filter: that excludes too much. Strip the query down to one keyword, confirm it returns results, then add operators back one at a time.
429 Too Many Requests. You exceeded 20 requests per second on the key. Back off for one second and retry. The Python and JavaScript examples above implement exponential backoff for this. The 20 req/s limit is universal across our plans; if you need higher throughput on a sustained basis, contact us about custom limits.
Cursor stops advancing or returns duplicates after deep pagination. This is the issue covered in the date-range chunking section. Any search index becomes unstable past a certain pagination depth on noisy queries. Switch to since:/until: weekly chunks.
Operator silently ignored. If a filter seems to have no effect, you are probably sending a web-search operator to the official v2 API, which drops the engagement filters. Confirm which operator set your provider accepts before assuming the query is wrong.
Operator cap. X's search index appears to silently fail queries with more than approximately 22 to 23 operators, and self-serve recent search on the official API caps the query string at 512 characters. If your query has more groupings than that and returns empty, simplify it or split it into multiple requests.
user object is missing or partial. The author was suspended, deleted, or made their account protected between the time the tweet was created and the time you requested it. The tweet still exists in the index but the author is no longer publicly enumerable. Handle this with tweet.get("user", {}) defaults in your code.
Unexpected language results despite lang:en. X's language detector is not perfect, especially on short tweets with hashtags or mixed scripts. For analytics workloads, post-filter with a language-detection library (such as langdetect or fasttext-langdetect) on the full_text field.
Private and shadow-banned accounts not appearing. Protected accounts are not in the search index. Suspended and locked accounts are also hidden. There is no operator to surface them. Shadow-banning is a separate diagnostic question from search visibility and is not something the search index can answer.
Frequently Asked Questions
How do I search tweets without the official Twitter API?
Use a third-party search API or an open-source scraper. Third-party REST APIs wrap their own scraping infrastructure behind an API key with no OAuth and full web operator support; Sorsa is one such alternative Twitter/X API, with a flat-rate /search-tweets endpoint that passes every operator through. Open-source libraries like Twikit work for small one-off jobs but are not stable enough for production.
How do I search tweets by hashtag using the API?
Pass the hashtag as a query term, for example #worldcup, and combine it with filters to control volume: #worldcup min_faves:50 lang:en -filter:retweets returns popular English original tweets carrying that hashtag. The official X API v2 matches #hashtag too but cannot apply engagement floors and limits recent search to about seven days, so for deeper hashtag history a flat-rate web-operator API is the more capable route.
Can I search tweets older than 7 days?
Yes, on third-party APIs. The Sorsa /search-tweets endpoint supports the full historical archive going back to 2006 with the since: and until: operators. The official X API v2 recent search is limited to about 7 days; full-archive search on the official API is restricted to higher-commit tiers.
How many tweets do I get per search request?
A single /search-tweets request returns one page of around 20 tweets. To pull more, use cursor pagination with next_cursor. For datasets above a few thousand tweets, combine cursor pagination with date-range chunking to avoid cursor drift on long walks.
What are the most useful Twitter search operators for developers?
The high-leverage operators in production code are from:, to:, min_faves:, min_retweets:, since:, until:, lang:, and the -filter:retweets and -filter:replies exclusions. Engagement filters are particularly valuable because they remove low-quality noise without losing relevant content, and because they do not work on the official X API v2.
Does the Twitter Search API support Boolean logic (AND, OR, NOT)?
Yes. Space-separated terms imply AND. OR in uppercase is explicit OR. Parentheses group expressions. A minus prefix excludes terms: crypto -scam. A full example is (bitcoin OR ethereum) min_faves:100 -filter:retweets lang:en.
How much does it cost to search tweets via API in 2026?
The official X API v2 costs around $0.005 per post read on pay-per-use, plus roughly $0.010 per author profile read, which comes to about $5.00 per 1,000 tweets before author data. Sorsa is flat monthly and starts with 100 free requests, no card required. Batched through /tweet-info-bulk, tweet data works out to about $0.02 per 1,000 tweets on the Pro plan (from $0.049 on Starter down to $0.018 on Enterprise), author profiles included; through the paginated /search-tweets endpoint at around 20 tweets per request it is closer to $0.10 per 1,000.
Can I search tweets in real time?
Practically yes, through polling. With a 20 req/s rate limit and order: "latest", you can poll a query every few seconds and get new tweets within seconds of posting. For true push-based streaming, the official X API filtered stream is the only option that delivers without polling. For most monitoring workloads, polling at 30 to 60 second intervals is sufficient and cheaper to operate.
Getting Started
You can test the endpoint before writing a line of client code. The interactive API playground runs real requests against your key from the browser, and the visual search query builder gives you a form for operators and outputs the exact JSON body to send.
When you are ready to pull a key: the first 100 requests are free with no card and cover all 40 endpoints, signup takes minutes with no developer-account approval, and every plan runs on the same flat 20 requests per second. Batched, the flat plans work out to as little as $0.02 per 1,000 tweets. Sign up at the Sorsa dashboard and follow the quickstart guide for the five-minute setup. If you are moving off the official API, the migration guide linked earlier maps each v2 endpoint to its Sorsa equivalent with code on both sides.
Reviewed by Keksich, founder of Sorsa, marketer and X API researcher.
How this guide was put together: it draws on our hands-on work building and operating Sorsa's search infrastructure, tested live against the /search-tweets endpoint, and on a direct comparison with the official X API v2 recent-search endpoint. Operator behavior was checked against the community-maintained igorbrigadir/twitter-advanced-search reference and the official X API documentation; pricing reflects the official X API per-read rates current as of July 8, 2026. Endpoint details come from the Sorsa API docs. More about who publishes this blog is on our about page.