By Sorsa Editorial

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

  1. Why Search Tweets Programmatically?
  2. The Twitter Search API Landscape in 2026
  3. The /search-tweets Endpoint: Request Anatomy
  4. What's in the Response
  5. Search Operators That Actually Work
  6. Two Operator Sets: Web Search vs the Official v2 API
  7. How Do You Search Tweets by Hashtag?
  8. Pagination: Collecting Thousands of Tweets
  9. Working Code: Python and JavaScript
  10. Real-World Query Templates
  11. Search Tweets vs. Track Mentions: Which Endpoint?
  12. How Sorsa Search Compares to the Official X API
  13. Common Errors and Troubleshooting
  14. Frequently Asked Questions
  15. 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

ParameterTypeRequiredDescription
querystringYesSearch keywords. Supports the full set of native X search operators.
orderstringNo"popular" (default) matches the "Top" tab in X search. "latest" returns chronological, newest first.
next_cursorstringNoPagination cursor from a previous response. Omit on the first request.

Minimal cURL Example

bash
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.

json
{
  "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 intelligence matches tweets containing any of these words
  • "artificial intelligence" matches the exact phrase
  • Word stemming is on by default: bear will also match bears

User-based

  • from:elonmusk tweets posted by an account
  • to:openai tweets replying to an account
  • @sorsa_app tweets mentioning an account

Engagement filters

  • min_faves:100 at least 100 likes
  • min_retweets:50 at least 50 retweets
  • min_replies:10 at 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-01 on or after this date
  • until:2026-03-01 before this date (exclusive)

Boolean logic

  • (bitcoin OR ethereum) min_faves:100 lang:en parentheses for groups
  • crypto -scam -airdrop exclusion 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:

OperatorWhat people get wrong
filter:verified vs filter:blue_verifiedfilter: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:7dA 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 @userfrom: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:

json
{
  "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:

  1. First request. Send query and order. Do not include next_cursor.
  2. Read the cursor. The response contains a next_cursor string.
  3. Next request. Send the same query, same order, plus the next_cursor value you just received.
  4. Repeat until next_cursor is null, 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:

python
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

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)

javascript
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:

python
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.

DimensionSorsa /search-tweetsOfficial X API v2 /2/tweets/search/recent
Pricing modelFlat monthly plans; from $0.02 per 1,000 tweets when batchedPay-per-use, ~$0.005 per post read (about $5.00 per 1,000)
Free to start100 free requests, no card, all endpointsNo free credit; buy credits before the first call
Author profile in responseEmbedded by default, no extra chargeSeparate user read, ~$0.010 each, via expansions=author_id
AuthAPI key in ApiKey headerOAuth 2.0 Bearer Token
Account approvalInstant signup, no approval queueDeveloper Console signup
Engagement operators (min_faves:, min_retweets:, min_replies:)YesSilently ignored
Web operator parityFull set passed throughSubset only, different syntax (has:, is:)
Historical archiveBack to 2006~7 days recent; full archive on higher-commit tiers only
Rate limit20 req/s, all plansCredit-based, varies
Real-time monitoringPolling at 20 req/sFiltered 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.