twitterapi.io is an independent third-party service. Not affiliated with X Corp.

Blogfilter twitter search

Twitter (X) Search Filters — Programmatic API Guide

By Alex Chen4 min read

Search filtering is the single biggest cost + signal lever in any programmatic Twitter (X) workflow. A broad query on a popular topic can return millions of results; the same query with well-placed filters returns a signal-only stream you can actually process at reasonable cost.

This guide walks the operator grammar, priority-ordered by which filters give the biggest volume-cut per addition, with runnable Python + a cost-math table that shows what each filter saves you.

01 — Section

The 4 operator classes

Engagement threshold (biggest cost lever): min_faves:N, min_retweets:N, min_replies:N. Any positive N cuts the result set by 90%+ on typical queries — most tweets get zero engagement. Start with min_faves:5 for signal-only pulls.

Content class: lang:xx (ISO code — en, es, ja, etc.), filter:media / filter:links / filter:verified / filter:replies, -filter:retweets (exclude retweets — very useful).

Author scope: from:@handle, to:@handle, list:, @mentioned_handle.

Date + phrase: since:YYYY-MM-DD, until:YYYY-MM-DD, "exact multi-word phrase", -term_to_exclude, (term1 OR term2).

Combine any of these space-separated inside the query string. Operators are additive (all conditions must match).

02 — Section

Runnable — filter every layer

One end-to-end query showing each operator class in action:

python
import os, requests

HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"

def search(query: str, max_pages: int = 10):
    tweets, cursor = [], None
    for _ in range(max_pages):
        params = {"query": query}
        if cursor: params["cursor"] = cursor
        r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=HEADERS, params=params, timeout=15)
        r.raise_for_status()
        resp = r.json()
        tweets.extend(resp.get("tweets", []))
        cursor = resp.get("next_cursor")
        if not cursor: break
    return tweets

# Broad → narrow, showing volume decay at each filter
queries = [
    'openai',                                                    # baseline
    'openai lang:en',                                            # + language
    'openai lang:en min_faves:10',                               # + engagement
    'openai lang:en min_faves:10 -filter:retweets',              # - retweets
    'openai lang:en min_faves:10 -filter:retweets since:2024-01-01 until:2024-02-01',  # + date window
]

for q in queries:
    n = len(search(q, max_pages=2))
    print(f"  {n:>5}  {q}")
# Typical output shows 10-100x volume reduction per added filter
03 — Section

Cost-saving math per filter

Every filter cuts returned volume → cuts your bill. Below is a rough magnitude table for a common baseline query, based on typical result-decay observed on our platform.

Filter addedTypical volume cutCost delta @ 100K baseline
baseline query (no filter)$15.00
+ lang:en40-60% cut$6-9
+ min_faves:1090-95% cut$0.75-1.50
+ -filter:retweets30-50% cut$0.40-1.00
+ since:/until: (30-day window)depends on query recencyvaries

Practical rule: for any research-grade pull, always add lang: + min_faves: before running — it usually saves 95%+ vs the raw query.

04 — Section

Common filter patterns

Signal-only sentiment pull: lang:en min_faves:20 -filter:retweets — high-engagement English original posts, ideal for sentiment / opinion analysis.

Cashtag longitudinal: $TICKER lang:en since:2024-01-01 until:2024-12-31 min_faves:5 — one-year window of retail investor mentions above noise floor.

Author-specific archive: from:@handle since:2020-01-01 until:2024-12-31 — full historical posts by a target account.

Media-only pull: filter:media -filter:retweets — tweets with images/video, deduplicated.

Deliberate exclusion: - - — brand monitoring with common noise removed.

Verified-only: filter:verified lang:en — narrower signal from verified accounts (analyst / journalist tier).

05 — Section

Alternative — X official /2/tweets/search/recent

Same operator grammar. /2/tweets/search/recent (Basic tier) or /2/tweets/search/all (Full-Archive Enterprise) per docs.x.com/x-api/getting-started.

Pricing: $0.005 per post per docs.x.com/x-api/getting-started/pricing on the Basic tier for recent-search; Full-Archive is Enterprise contract-priced.

Wins when: already on X Developer Console for other workloads. Loses when: cost-sensitive and only need read access — 33× cost delta vs twitterapi.io per direct pricing comparison.

06 — Section

Comparison — 3 filter-capable paths

Dimensiontwitterapi.ioX official BasicApify actor
Endpoint/twitter/tweet/advanced_search/2/tweets/search/recentactor call
Operator grammarX native (min_faves, since, etc.)X native (same)limited subset per actor
Cost per tweet$0.00015 (twitterapi.io/pricing)$0.005 (docs.x.com)$0.0004-$0.002 (apify.com/pricing)
Historical (since:2018)Enterprise only✓ (with tier)
AuthX-API-KeyBearer + X Dev ConsoleApify token
Best forany filter-heavy workflowX-native, on Enterprisemulti-platform pipelines
07 — Section

Debug tips — what to do when 0 results

Over-filtered: too many operators — remove the most restrictive one (usually min_faves if high, or the date window).

Typo in operator: min_favorites: (wrong) vs min_faves: (right) — X's grammar is strict. Copy from a working example.

Language mismatch: lang: uses ISO 639-1 (en, not english; zh, not chinese).

Date-window all in past + tier gate: if your since: is >7 days ago on X official Basic, the tier gates you out. Use twitterapi.io for historical windows.

python
# Practical example: signal-only brand monitoring with layered filters.
import os, requests, json
from pathlib import Path
from collections import Counter

HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"

BRAND = "stripe"
COMPETITORS = ["paypal", "square", "adyen"]
EXCLUDES = " ".join(f"-{c}" for c in COMPETITORS)

# Layered filter: brand + no-competitor + engaged + English + no-retweets
query = f"{BRAND} {EXCLUDES} lang:en min_faves:5 -filter:retweets"

tweets, cursor = [], None
for _ in range(10):
    params = {"query": query}
    if cursor: params["cursor"] = cursor
    r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=HEADERS, params=params, timeout=15)
    r.raise_for_status()
    resp = r.json()
    tweets.extend(resp.get("tweets", []))
    cursor = resp.get("next_cursor")
    if not cursor: break

print(f"signal-only pull: {len(tweets):,} tweets")
# Sentiment proxy: engagement distribution
faves = Counter()
for t in tweets:
    n = t.get("favorite_count", 0)
    bucket = "low(5-20)" if n < 20 else "mid(20-100)" if n < 100 else "high(100+)"
    faves[bucket] += 1
for bucket, count in faves.most_common():
    print(f"  {bucket}: {count}")

# Cost per twitterapi.io/pricing:
#   len(tweets) × $0.00015
#   Same query WITHOUT filters would return ~50x more tweets → ~50x cost
08 — Questions

Questions readers ask

Does the operator grammar work identically on twitterapi.io and X official?

Yes — twitterapi.io uses X's native search grammar directly. Any operator that works in X's advanced search UI works in the API query parameter (min_faves, since, until, lang, from, filter:, exclusions with -term, exact phrases with quotes, OR groupings).

Can I combine 10+ filters in one query?

Yes. Query strings can be very long (up to a few KB before X's own parser starts complaining). Practical max is ~15-20 operators combined. Over-filtered queries often return 0 results — remove the most restrictive filter and re-run to debug.

What's the biggest cost-saver filter?

min_faves:N almost always. Most tweets on any topic get zero engagement — adding even min_faves:5 typically cuts 90%+ of results. For signal-only analysis workflows this is the default first filter.

How do I filter out spam and bot accounts?

There's no explicit filter:spam operator. Post-process approach: pull with min_faves:5 + -filter:retweets (already cuts most spam), then filter by author followers_count > 100 in your downstream code.

Does `filter:verified` still work post-Twitter Blue?

Yes but semantics changed — filter:verified now includes anyone with a blue checkmark (paid subscription). For legacy-verified only, use follower-count post-filter or check the verified_type: 'blue' field in the response.

Can I use regex-style filters?

No — the search grammar is strict operator+value pairs. For regex-style matching (case patterns, character classes), pull with your best keyword approximation then regex-filter results in your downstream code.

What happens if my `since:` date is before 2006?

Twitter launched March 21, 2006 — dates before that return zero results (no data exists). Both twitterapi.io and X official return empty result sets rather than error for pre-launch dates.

09 — Further reading

Continue

Sources & further reading
More from this series
Build it

Stop reading. Start building.

Starter credits cover real testing on real data. Google sign-in, no card, no application queue.

Get an API key
    Twitter (X) Search Filters via API — Guide | TwitterAPI.io