What are the Twitter advanced search operators?
Last updated August 24, 2026
Operators are prefixes you stack inside one query string. from:nasa pins an author, min_faves:500 sets an engagement floor, since:2026-01-01 opens a date window, lang:es restricts language, double quotes lock an exact phrase and a leading minus negates. TwitterAPIs forwards the whole string to tweet/advanced_search, where a call reads roughly 20 posts for $0.0008.
Every rate here is the pricing TwitterAPIs publishes. The billed rate is $0.0008 per call; $0.04 per 1,000 tweets is derived from it at a full 20-tweet page, which is the default page size rather than a guaranteed yield (source: twitterapis.com/pricing).
The grammar underneath every query
Whitespace between two terms means AND, so mars rover requires both words and the parenthesised form (mars rover) resolves to the identical set. There is no AND keyword to type and no NOT keyword either. OR widens a match but has to be capitalised, because a lowercase or is read as an ordinary search word rather than a boolean, which is the single most common reason a query parses cleanly and returns the wrong set. Everything else in the vocabulary is a prefix operator written as name:value with no space after the colon, and each clause you add narrows the result rather than widening it unless you have explicitly introduced an alternation. Hold those two facts and the rest of this reference is vocabulary.
Quoting, wildcards and pinned spellings
Double quotes lock wording in order and switch spell correction off, so "machine learning" will not quietly match a corrected variant. An asterisk inside a quoted phrase stands in for exactly one word, which makes "best * of 2026" match a whole family of phrases in a single clause. A leading plus pins a literal spelling against autocorrect, which matters for product names the classifier keeps helpfully rewriting, as in +kubernetes. Hashtags and cashtags are matched by their own sigils, #opensource and $NVDA, and neither needs quoting. url:host.com matches a tokenised domain anywhere in the body, and a hyphenated host has to be written with an underscore instead, so url:t_mobile.com rather than the literal spelling of the domain.
Grouping, precedence and the trap in an unwrapped OR
Operators stack with spaces and every clause applies at once, which is why from:elonmusk min_faves:500 since:2026-01-01 behaves exactly as written. The trap is OR, which binds loosely rather than the way most people read it. In bitcoin OR eth min_faves:1000 the engagement floor attaches to the eth side alone and leaves every bitcoin post ungated, which presents as the operator being ignored. Wrap the alternation, as in (bitcoin OR eth) min_faves:1000, and the floor covers both. Exclusions behave identically, so apple -fruit OR juice needs explicit grouping too. Parenthesise every OR cluster by habit, build long strings from the inside out, and treat each parenthesised group as one unit rather than as each of its terms.
Negation, and the two operators that refuse it
A leading minus negates, and it takes more shapes than most references show. It works on a bare word, so apple -fruit drops the wrong sense of an ambiguous term. It works on a quoted phrase, as in -"hot take". It works on a filter, so -filter:replies strips replies and -filter:retweets removes the old RT text style and native reposts together in one clause. It also works on the engagement operators, where it does something different in kind rather than degree: -min_faves:25 does not loosely exclude popular posts, it inverts the floor into a ceiling. Two operators cannot be negated at all. There is no -list: form and no way to invert filter:follows, so plan both as inclusions only.
Scoping by author, mention and list
from:handle keeps only posts that account wrote. to:handle keeps replies aimed at it, while a bare @handle matches anything naming it, so @nasa -from:nasa isolates mentions and drops the account's own posts. Those three get confused constantly and they return very different sets, so pick by the job: from: for timeline work, @ for mention tracking, both joined by OR when you want the full picture around an account. list:owner/slug and list:84839422 restrict results to a named list's members, addressed either by path or by numeric id. filter:verified catches the pre-2023 legacy badge while filter:blue_verified catches paid accounts, and stacking filter:verified -filter:blue_verified isolates the legacy pool. filter:follows narrows to accounts the searching session already follows.
Engagement floors, and the ceilings nobody mentions
min_faves:N, min_retweets:N and min_replies:N each set a lower bound, so solana min_faves:1000 drops everything quieter than that. Negating the same operator inverts it into a ceiling, which is the half almost nobody uses: -min_faves:25 returns posts holding fewer than twenty five likes, and -min_replies:1 isolates posts nobody answered at all. filter:has_engagement keeps anything that drew a like, repost, reply or quote, and its negated form finds the posts that landed completely flat. These gates are the reason to pre-filter rather than post-filter, since a floor applied upstream means you never pay to pull the noise in the first place. On a broad topic term a five-thousand-like floor strips the overwhelming majority of the volume before a single row is billed.
Bounding time by date, epoch or post id
since: is inclusive and until: excludes its own day, both taking YYYY-MM-DD. Append _HH:MM:SS_UTC to either and you can slice below a day boundary, which matters when a topic moves hour to hour. since_time: and until_time: take Unix epoch seconds instead, convenient when your upstream system already stores timestamps that way. since_id: and max_id: bound by post id, which works because those ids increase monotonically and are therefore time-sortable, so a higher id is always the later post. within_time:2d, 3h, 5m or 30s gives a rolling window measured back from the moment you run it, which makes it the wrong choice for anything you need to reproduce later. One rule catches everyone: a time bound alone returns nothing and must ride alongside a keyword, a handle or a hashtag.
Post kind and conversation scoping
filter:replies keeps replies, and its negation strips them for a clean original-content set. filter:self_threads narrows to an author's own reply chains. filter:quote keeps posts carrying a quoted original. filter:retweets covers both the old RT text style and quote posts, while filter:nativeretweets targets retweet-button reposts and reaches back only about seven to ten days; include:nativeretweets adds those back to a query that would otherwise drop them by default. conversation_id: followed by a post id returns every post in that one conversation. quoted_tweet_id: returns the posts quoting a given post, and quoted_user_id: widens the same idea across a whole account. Those last three are how you pull a discussion through search rather than through the dedicated post routes.
Attachment filters, and how they combine
filter:media catches any attachment at all, and the narrower forms split by kind: filter:images for a still from any source, filter:twimg for natively hosted images only, filter:videos for any video including embeds, filter:native_video for platform-hosted video, plus filter:consumer_video and filter:pro_video for the standard and amplify variants. filter:spaces keeps live audio posts. Three more key off what the body contains rather than what is attached to it, namely filter:links, filter:hashtags and filter:mentions. filter:news restricts to whitelisted news domains and filter:safe drops sensitive content. Combining is where the family earns its keep, because subtraction is available too: from:nasa filter:media -filter:images returns that account's video and GIF posts with every still image removed, which no single positive filter expresses on its own.
Language codes, and the pseudo-codes nobody documents
lang: takes ISO 639-1 two-letter codes, lang:en, lang:es, lang:ja, lang:hi and the rest, and runs against the platform's own classifier rather than yours. On short or mixed text that classifier guesses wrong often enough to matter, which is why a second family exists alongside it. The pseudo-codes sort by the shape of the content instead of its language: und for text it could not classify, qam for posts carrying only mentions, qct for cashtags only, qht for hashtags only, qme for media links only, qst for very short text, and zxx for content with no text at all. That last one is the genuinely useful one, since from:user lang:zxx filter:images returns image-only posts with every captioned one excluded.
Posting client, place, and the operators that are dead
source: filters by the app that published a post, with spaces and hyphens becoming underscores, so twitter_for_iphone, twitter_for_android, twitter_web_app, tweetdeck and twitter_ads all work as written. The location family, near:"City Name", near:me, within:10km, geocode:37.7764,-122.4172,10km and place:, still parses but matches a shrinking pool because exact geotagging was retired for most posts, so use it to narrow a keyword query and never as the backbone of one. filter:vine and filter:periscope parse without error and return nothing, the worst failure mode available, because a pipeline can sit empty for days. card_name: retains roughly seven to eight days. A query caps out around 22 to 23 operators, past which the whole string fails silently rather than partially matching.
Where the string is evaluated, and what it costs
The finished string goes into the query parameter of GET /twitter/tweet/advanced_search and is evaluated server-side exactly as it would be on the twitter.com advanced search screen, with an Authorization Bearer header carrying your key. product chooses the ranking, defaulting to Latest, with Top, People, Photos and Videos also accepted; it changes the ordering and the result flavour, never which operators fire. Results arrive roughly twenty at a time and you read past that by feeding next_cursor back in as cursor rather than by asking for a larger page. One call is a standard read at $0.0008, which puts a thousand posts near four cents, and the whole key runs under one ceiling of 600 requests a minute with 20 concurrent. That figure is ours: $0.0008 a call, per our pricing page.
Does X have an advanced search page?
Yes. On the web it lives at x.com/search-advanced, and X's help center describes it as available when you are logged in to x.com. You can reach it two ways: type a search into the search bar, then choose Advanced search under Search filters at the upper right of the results page, or open More options and pick Advanced search there. The help center documents four groups of fields. Words covers all of these words, an exact phrase, any of these words, words to exclude, hashtags and language. People covers posts from an account, replies to an account and posts mentioning an account. Places covers posts sent from a geographic location. Dates covers before, after or between two dates, reaching back as far as the first public post. X's own help examples write those fields out as an operator string in the search address, with since:, until:, lang:, near: and within: spelled in full, so the form and the typed operators are two views of one query language. The rest of this page documents operators you type directly, such as the filter: family, the engagement floors, the pseudo language codes and the id bounds.
Is X advanced search free?
For a person using x.com, yes in the sense that matters: X's help center lists one condition for the advanced search form, being logged in to x.com, and names no fee for it. The cost appears when the same search runs in code. On the official API, search results are billed per post returned on the pay-per-use meter, $0.005 a post, the official operator set differs from the web one, and query length is capped at 512 characters for recent search and 1,024 for full archive search on self-serve access. Here the web operator string is passed unchanged to tweet/advanced_search, where a call returns roughly 20 posts and bills $0.0008, so a thousand matching posts cost about four cents. The $0.50 starting credit covers about 625 searches with no card, which is enough to test a set of saved queries before anything is paid. Free and programmatic are different questions, then. The form is free to use by hand, and a price only starts when a script repeats the search on a schedule, at which point the meter you choose decides whether it costs cents or dollars.
Why you need more than the basic search bar
The basic search bar takes words and returns a mix of posts ranked by X, which is fine for a quick look and wrong for anything you want to repeat. Typed plainly, it cannot pin an author, set an engagement floor, fix a date window, drop replies or restrict language, so its results drift with whatever X ranks as relevant that hour. Operators turn the same box into a precise filter. The difference between from: and a bare @ shows why it matters: from:nasa returns posts the account wrote, while @nasa returns posts that name it, and a handle typed without either is just another word to match. Precision has a cost side too once a search runs in code. Every page you read is billed, so a query that returns twenty relevant posts per page is several times cheaper per useful row than one returning three relevant posts and seventeen of noise. Learn five operators, from:, quoted phrases, min_faves:, since: with until:, and -filter:replies, and most searches stop needing a second pass to clean them up.
Keyword logic and phrase matching
Keyword logic on X comes down to three rules. A space means AND, so every bare word must appear. OR, in capitals, means either side may match, and a lowercase or is simply a word. A minus means exclude. Phrase matching sits on top of that: double quotes require the words in that order, with spell correction switched off, and an asterisk inside the quotes stands for exactly one word. Put together, a buyer research query reads naturally: (crm OR "customer relationship") "switching from" -hiring. That asks for either term, requires the exact phrase, and drops recruiting posts. Two habits prevent most mistakes. Quote any multi-word product name, because otherwise its common words match everywhere on their own. And wrap every OR in parentheses, because an unwrapped OR binds loosely and lets other clauses attach to one side only. When a quoted phrase returns nothing, test it without the quotes before abandoning it, to see whether the words appear in another order or form. A plus sign before a word pins its spelling against autocorrect, which helps with brand names the classifier keeps rewriting.
Building queries that combine operators
Build a query from the inside out and test each layer before adding the next. Start with the topic as a parenthesised group of terms, ("vector database" OR pgvector OR qdrant). Run it and read a page. Add the scope, lang:en -filter:replies, and run it again. Add the quality gate, min_faves:10. Add the time window last, since:2026-09-01, because a window hides problems in the earlier layers by shrinking the sample. Each layer narrows, so if a step empties the result, the step you just added is the cause. Combining has a ceiling: a single query holds roughly 22 to 23 operators before it fails silently, and on the official API the whole string is capped at 512 characters for recent search and 1,024 for full archive. Most good queries need five to ten operators. When a query outgrows that, split it by topic and run the halves as separate searches, deduplicating on post id afterwards, rather than forcing everything into one string. Two smaller queries cost two calls a page instead of one, and they are far easier to debug than one long query that silently returns nothing.
How to exclude tweets from search results
Exclusion is where most of a query's precision comes from, and a short set of patterns covers nearly every case. Drop an account's own posts from a mention search with @brand -from:brand. Drop reposts with -filter:retweets, and replies with -filter:replies, when you want original statements only. Drop a wrong sense of an ambiguous word by excluding the word that marks it, as in jaguar -car. Drop giveaway and promotion noise by excluding its vocabulary, -giveaway -"retweet to win". Drop a whole phrase from an otherwise good result with a minus in front of the quotes. Invert an engagement gate only when you mean to, since -min_faves:25 keeps posts below twenty-five likes rather than removing them. Exclusions stack, so a mention monitor might read @brand -from:brand -filter:retweets -giveaway. Two operators cannot be negated at all, list: and filter:follows, so plan those as inclusions only. And because the price here is per call, every irrelevant post an exclusion removes makes each billed page denser with posts you actually wanted, which lowers the cost per useful row without changing the rate.
How to search tweets by date
In the web form, open Advanced search, fill in the Dates group with a from date, a to date or both, add at least one word or account, and submit. In a typed query the same thing is since: and until: with dates written YYYY-MM-DD: from:nasa since:2025-07-01 until:2025-08-01 returns that account's posts from July 2025, because since: includes its own day and until: excludes its own. A date bound needs company, so pair it with a keyword, a handle, a hashtag or a filter, since a bare date returns nothing. For a slice narrower than a day, append a time in UTC, as in since:2025-07-04_09:00:00_UTC, and for systems that store Unix time, since_time: and until_time: take epoch seconds. In code, the string goes into the query parameter of tweet/advanced_search with product=Latest, and you page with the cursor until a page comes back empty. A month of a busy account might hold a few hundred posts, a dozen or so calls at $0.0008 each. To pull a long period, split it into monthly windows and run them in parallel, which also makes a failed window cheap to repeat on its own.
Date filters that survive a re-run
A date filter that works today can return a different set next month, and the cause is usually the choice of filter rather than the data. within_time:7d measures back from the moment the query runs, so the same string pulled on two different days covers two different weeks; use it for live monitoring and never for anything you need to reproduce. since: and until: with absolute dates fix the window, which is what a report, an audit or a dataset needs. Add the explicit _UTC time suffix when the boundary matters to the hour. For the strictest reproducibility, bound by post id with since_id: and max_id:, which pins the edges to two specific posts rather than to clock times. One more source of drift sits outside the query entirely: posts deleted after your first pull vanish from the second, and engagement counters keep moving, so a re-run of a fixed window can legitimately return fewer rows with higher counts. Store the query string, the run time and the ids you received together, and any later difference can be explained from the record rather than argued about from memory.
The snowflake ID trick
Every post id on X is a 64-bit Snowflake id, and X documents that it encodes when the object was created, alongside a worker number and a sequence number, which makes ids roughly time-ordered: a higher id is generally a newer post. That turns ids into time bounds. since_id: and max_id: bound results by post id, so if you know the id of a post at the start of a window and one at its end, you can fence the search to that range more precisely than a calendar date allows, and the fence stays fixed however often the query is re-run. The practical trick for deep paging is to note the oldest id on each page you collect and use it as max_id: for the next query, which walks backwards through a busy topic in independent, restartable steps rather than in one long cursor chain. Two rules keep it working. Treat ids as strings, since X itself warns that languages limited to 53-bit integers, JavaScript among them, lose precision on large ids. And because the ordering is rough rather than exact across servers, overlap adjacent windows slightly and deduplicate on id.
Geo searches and the location operators
Location search on X rests on a handful of operators, and all of them now match a shrinking pool. near:"City Name" matches posts tagged near a place, within:10km sets the radius around it, geocode:37.7764,-122.4172,10km takes a latitude, longitude and radius directly, and place: restricts to a tagged place. X's own help examples still combine them, near:"San Francisco, CA" within:15mi with a date range, and the advanced search form keeps a Places group for posts sent from a city, state or country. The catch is that exact geotagging was retired for most posts, so only a minority carry the location these operators read. Use geo operators to narrow a keyword query, never as its backbone: (concert OR gig) near:"Austin, TX" within:25mi since:2026-03-01 returns a useful sample, while near:"Austin, TX" on its own returns a thin and skewed one. For audience location, profile data is the better source: the About object returns the country an account is based in, and audience_summary builds a country histogram from a follower sample. Treat geo search results as indicative rather than complete.
Language and source filters in practice
Two filters do much of the work of cleaning a sample before you read it: language and source. lang: restricts posts to an ISO 639-1 language as classified by X, which holds up on full sentences and wobbles on short or mixed text, so for analysis pair it with a minimum length check in your own code rather than trusting it alone. The pseudo-codes help from the other direction: lang:und finds text X could not classify, which is where short replies and emoji-only posts collect. source: filters by the app that published a post, so source:twitter_ads isolates promoted posts and lets you measure how much of a topic's volume is advertising. A Spanish-language brand sample might read marca lang:es -filter:retweets, with a second query on source:twitter_ads to size the paid share beside it. Note where the classification happens: X assigns the language, so a disagreement with your own detector is a property of the data rather than a bug in your query. Keep the lang field from each result in your store, and your filter decisions can be audited later, which matters the first time a report's numbers are questioned.
Find popular, high-engagement posts on a topic
Finding the posts that drew the most response on a topic takes two levers: an engagement floor and the ranking mode. The floor, min_faves:, min_retweets: or min_replies:, removes everything quieter than your threshold before a single row is returned, so solana min_faves:1000 lang:en is already a list of popular posts. Set the threshold from the size of the topic rather than a fixed number: a niche developer tool may peak at fifty likes while a consumer brand clears ten thousand. The ranking mode decides the order: product=Top returns the most engaged matches first, while Latest returns the newest, so popular posts from a recent window come from Top with a since: bound. Add -filter:retweets to stop one viral post appearing many times, filter:media when you want posts with images or video, and filter:quote when you want posts that commented on something else. min_replies: is the floor to use for posts that started conversations rather than collected likes. Each page is one $0.0008 call, and because the floor runs before results are returned, a high threshold makes every billed page denser with what you came for.
Best tweets on a topic from specific users
When the question is what a set of named people said about a topic, combine an author group with the topic and let engagement pick the best. The pattern is (from:alice OR from:bob OR from:carol) topic min_faves:50, with the author group in parentheses so the OR stays contained. Swap from: for to: to get the replies people sent to those accounts about the topic, which is often where the frankest discussion sits, and use @handle when you want any post naming them. For a larger group, list: does the same job without a long string: list:84839422 kubernetes min_faves:20 searches only the members of that list and keeps the query well under the operator ceiling. quoted_user_id: finds posts quoting any post from one account, a quick way to see how others framed that person's statements. Run with product=Top for the best-received posts or Latest for the newest, and add a since: bound for a review period. Each page is one call, so reviewing a ten-person panel once a month costs a few cents, and the same string can be re-run next month to compare.
Find top tweeters on any topic
Search returns posts, but the author sits inside every row, so ranking the people who post most on a topic is an aggregation you run over the results. Query the topic with a quality floor, for example "edge computing" min_faves:5 -filter:retweets lang:en since:2026-09-01, page through the window, and group the posts by the author id nested in each one. Count posts per author, sum their likes and replies, and you have two rankings, most active and most engaging, which are rarely the same list. Exclude your own account with -from:yourbrand, so the ranking reflects the wider conversation rather than your own posts. Then take the top twenty authors to user/info or the batch About route for bios and follower counts, which separates practitioners from aggregators. A window of 2,000 matching posts is about 100 calls, eight cents, and the author data is already inside them at no extra charge. Rerun the same query monthly and the movement in the list, who arrived and who went quiet, is often more useful than any single month's ranking.
Competitor intelligence without the noise
Watching a competitor on X produces noise fast unless the queries separate three different conversations. What they say: from:competitor -filter:replies gives their original posts, the announcements and positioning. What others say about them: @competitor -from:competitor -filter:retweets gives posts naming them, minus their own voice and minus repeated reposts. What their customers say to them: to:competitor gives replies addressed to the account, usually the frankest signal of what is working and what is not. Add an engagement floor to the second query, min_faves:5, to drop drive-by mentions, and a time bound for a weekly review. For announcements that link out, url:competitor.com finds posts sharing their pages, including posts that never name the account. Keep the three queries separate rather than merging them with OR, because each answers a different question and mixing them loses which is which. A weekly pull of all three for one competitor is typically a few dozen calls, under five cents, and a free monitor on their account delivers each new original post as it appears, without any polling.
Competitor lead generation
The most useful leads in a competitor's audience are the people telling them, in public, that something is wrong. to:competitor (slow OR broken OR expensive OR cancel) -filter:retweets surfaces replies addressed to the competitor that contain complaint language. A second query catches people comparing options: ("alternative to" OR "switching from" OR "moving off") competitor. A third catches people asking others for experience: "anyone using" competitor. Add lang: for your markets and a since: bound so you only work recent signals. Each result carries its author, so the next step is a batch About read for job titles and companies, then a batch relationship check to see who already follows you. Two cautions apply. A complaint is a signal of interest rather than consent to be contacted, so engage in public where you can genuinely help instead of sending unsolicited messages, which X's automation rules restrict. And put the competitor's name in quotes if it is also a common word, or the query fills with unrelated posts. A weekly sweep of these three queries costs cents and produces a short list worth a person's time.
High-intent buyer discovery
Buyer intent on X shows up as a handful of phrases people use when they are about to choose something. ("looking for" OR "any recommendations" OR "can anyone recommend" OR "what do you use for") followed by your category term catches people asking. Pair it with -filter:retweets so a single popular question does not fill the page, and -from:yourbrand to keep your own posts out. Recency matters more here than anywhere: a request posted today is worth answering, and one from last quarter is usually settled, so run these with product=Latest and a within_time:3d window for live work, switching to absolute dates only for retrospective analysis. Engagement floors work against you on this job, because most genuine questions collect few likes. Filter on the author afterwards instead: a batch About read shows whether the asker's profile fits your market, and a relationship check shows whether they already know you. Answer in public, helpfully and without a hard pitch, and let the thread do the rest. A loop that runs these queries a few times a day costs a few calls each time.
Influencer discovery with operators
Influencers in a niche are the accounts whose posts on a topic draw consistent engagement, and operators find them faster than a directory. Query the topic with a meaningful floor and original posts only, ("product design" OR "design systems") min_faves:200 -filter:replies -filter:retweets lang:en, across a few months with since: and until:. Group the results by author and keep the accounts that appear repeatedly, since one viral post is luck and five is a pattern. filter:blue_verified narrows to paid verified accounts if that matters for the campaign. Then check what matters beyond reach: the batch About route for bio, location and account age, and audience_summary on the shortlist, which samples followers and returns a country histogram plus a likely_bot_count from profile signals, so an account whose followers sit in the wrong market or look automated can be dropped before outreach. Beyond operators, follower graphs extend the list, since the verified followers of a known influencer are often other influencers in the same space. The full pass, a few hundred search pages plus a shortlist audit, typically costs under a dollar.
Content ideas and trends
The posts that already worked on a topic are the best brief for what to write next, and operators surface them in a minute. Run topic min_faves:500 -filter:replies with product=Top over the last quarter to see the formats and angles that drew response; read twenty and the patterns, threads, data posts and contrarian takes, become obvious. Swap the floor for min_replies:50 to find the posts that started arguments, which point to questions people still want answered. filter:links with an engagement floor shows which articles a community shared, and url:yourdomain.com shows how your own pages travelled. ("how do I" OR "how do you") with your topic collects the questions your audience asks in its own words, which is ready-made material for documentation and posts. For trends rather than evergreen ideas, pair the trends route, which returns current trending topics by country or location id, with a search on a trending term to read what is actually being said about it. Run the evergreen queries monthly and the trend check daily, and keep a log of each query beside the ideas it produced so the useful ones get reused.
Copy-paste queries you can use right now
These strings work as written in the x.com search box and, unchanged, in the query parameter of tweet/advanced_search; swap the placeholder terms for your own. Mentions of a brand without its own posts: @brand -from:brand -filter:retweets. An account's original posts this year: from:handle -filter:replies since:2026-01-01. Viral posts on a topic: topic min_faves:5000 -filter:retweets. Posts mentioning you that nobody answered: @brand -min_replies:1. Image-only posts from an account: from:handle lang:zxx filter:images. Native video on a topic: topic filter:native_video min_faves:100. Posts linking a domain: url:example.com -filter:retweets. Questions about a category: ("any recommendations" OR "looking for") category. Replies a competitor received: to:competitor -filter:retweets. A whole conversation: conversation_id:1234567890123456789. Posts quoting one post: quoted_tweet_id:1234567890123456789. A topic in Spanish this week: topic lang:es within_time:7d. A hashtag in English with traction: #opensource lang:en min_faves:50. Posts that drew long threads: topic min_replies:100. A list's members on a topic: list:84839422 topic. Each is one call per page at $0.0008, about 20 posts at a time, and the cursor in each response continues the same query.
Best practices for efficient search workflows
A search workflow that runs every day needs the same disciplines as any other data pipeline. Save every query as a string under version control, with a note of what it is for, so a change in results can be traced to a change in the query. Test each new query on one page before scheduling it, and read the rows rather than counting them. Use absolute date bounds for anything you report on, and within_time: only for live monitoring. Page with the cursor until a page comes back empty rather than to a fixed count, and record the cursor beside each page you store so a stopped run can resume. Deduplicate on post id across queries, since overlapping searches return the same posts, and keep ids as strings. Run independent queries in parallel, up to 20 at once on one key, rather than in a long serial loop, and back off on a 429 using Retry-After. Store the fetch time with every row, because counters keep moving. Finally, review the query set every quarter: operators do get retired, and a query that quietly returns nothing is the costliest failure of all, because nobody notices it.
Developer troubleshooting when search is not working
When a query returns nothing or the wrong set, work through the usual causes in order. A lowercase or is read as a word, so capitalise OR. An unwrapped OR lets other clauses attach to one side only, so parenthesise it. A date bound with no other term returns nothing, so pair since: and until: with a keyword, handle or filter. A space after a colon breaks an operator, so write from:nasa rather than from: nasa. A hyphenated domain in url: has to be written with the _ character in place of each hyphen. A query past roughly 22 to 23 operators fails silently, so split it. Some operators are dead: filter:vine and filter:periscope parse and return nothing, and filter:nativeretweets reaches back only seven to ten days. On the official API there is another class of error: min_faves: and min_retweets: are rejected with a 400 in favour of min_likes: and min_reposts:, operators such as is:retweet and has:media must be combined with a standalone term, and the string is capped at 512 or 1,024 characters. Here, a 400 means a malformed request, a 401 a bad key and a 402 an empty balance, so read the status code before rewriting the query.
Using operators through an API: web versus official syntax
There are two operator dialects for X search, and code written for one does not always run on the other. The web dialect is what the x.com search box and its advanced form use: from:, to:, since:, until:, min_faves:, min_retweets:, the filter: family and the rest of this page. The official X API uses its own documented set. Its engagement operators are min_likes:, min_replies: and min_reposts:, and its documentation states that min_faves: and min_retweets: are not valid there and are rejected with a 400. It expresses post types as is:retweet, is:reply and is:quote, and attachments as has:media, has:images and has:links, several of which must be combined with a standalone term rather than used alone. Its operator reference does not list since:, until: or the filter: family. Query length is capped at 512 characters for recent search and 1,024 for full archive on self-serve access. tweet/advanced_search here takes the web dialect unchanged in its query parameter, with product choosing Latest, Top, People, Photos or Videos, so a query tested in the browser runs in code as written. Moving a query over from the official API means checking each operator against its web name, starting with the engagement floors.
Operator families at a glance
| Family | Example | What it narrows |
|---|---|---|
| Author and audience | @nasa -from:nasa | Mentions of an account, minus its own posts |
| Engagement floor | solana min_faves:1000 | Posts holding a thousand likes or more |
| Engagement ceiling | -min_faves:25 | Posts under twenty five likes |
| Date window | nvda since:2026-01-01 until:2026-03-01 | A calendar range, end bound exclusive |
| Rolling window | bitcoin within_time:3h | The last three hours, measured at run time |
| Conversation | conversation_id:1888888888 | Every post in one exchange |
| Attachment | from:nasa filter:media -filter:images | Video and GIF posts, stills removed |
| Language | mundial lang:es | One auto-detected language |
This dictionary documents every available field for each object type.
Questions and answers
- Do operators have to be capitalised?
- Only OR does. The prefixes themselves are lowercase, as in from:, lang: and filter:, but a lowercase or is treated as an ordinary search word rather than a boolean, so the query silently means something other than what you wrote. That single rule accounts for a large share of strings that parse cleanly and return the wrong set, because nothing anywhere in the response warns you that it happened.
- How do I exclude something from results?
- Put a minus directly in front of it, with no space. It works on a bare word, on a quoted phrase, on a filter such as -filter:replies, and on the engagement operators, where -min_faves:25 flips a floor into a ceiling rather than merely excluding. Two things resist it entirely: list: and filter:follows cannot be negated at all, so there is no -list: form to reach for and no way to invert a follows restriction.
- Why does my OR query ignore the engagement floor?
- Because an unparenthesised OR binds loosely. In bitcoin OR eth min_faves:1000 the floor attaches only to the eth side, leaving every bitcoin post ungated, which reads exactly like the operator being ignored. Wrapping the alternation fixes it, so (bitcoin OR eth) min_faves:1000 applies the floor across both sides. Group every OR cluster as a matter of habit and the ambiguity disappears, along with most queries that look right and return wrong.
- Can I search a date range with no keyword?
- No. A time operator on its own returns nothing, so since:2026-01-01 has to ride alongside a keyword, a handle, a hashtag or a filter. A bare date bound is read as an empty query rather than as a broad one. Pair it with something concrete first, then narrow using the _HH:MM:SS_UTC form when a single calendar day still returns more volume than you want to page through.
- How do I search inside one conversation?
- conversation_id: followed by a post id returns every post in that conversation, which is what you want when the whole exchange matters rather than one level of it. quoted_tweet_id: does the neighbouring job, returning the posts that quote a given one, and quoted_user_id: widens that across an entire account. All three take numeric ids and page on the ordinary cursor, so nothing about the calling pattern changes.
- Does the ranking mode change which operators work?
- No. product only changes the ordering and the result flavour, with Latest as the default and Top, People, Photos and Videos as the alternatives. The same operator string is evaluated either way, so a query tested on Latest behaves identically when you switch it. Choose by the job instead: Latest when monitoring a live query where recency is the point, Top when the most engaged matches beat the freshest ones.
- Which operators are dead or unreliable in 2026?
- filter:vine and filter:periscope still parse and simply return nothing, which is worse than an error because a pipeline can sit empty for days without anyone noticing. The geo family, near:, within: and geocode:, matches a shrinking pool since exact geotagging was retired for most posts. filter:nativeretweets reaches back only seven to ten days, card_name: about seven to eight, and filter:verified behaves inconsistently since the badge rebrand.
- Is there a limit on how many operators one query can hold?
- Roughly 22 to 23, and going past it fails silently with no error rather than returning a partial match, which is the expensive kind of failure. Most useful queries land between five and ten anyway. When a query genuinely needs more, group related conditions in parentheses, since a parenthesised alternation counts as one expression toward the ceiling rather than as each of the terms inside it.
- How do I filter to posts that contain only an image?
- Combine a media filter with a pseudo-language code. lang:zxx matches content carrying no text at all, so from:user lang:zxx filter:images returns image-only posts with none of the captioned ones mixed in. The other pseudo-codes work the same way: qht for hashtag-only posts, qam for mention-only, qct for cashtag-only, qme for bare media links, and und for text the classifier could not place anywhere.
- What does running these queries cost?
- One search request is a standard read at $0.0008 and returns roughly twenty posts, which works out near four cents per thousand. Adding operators does not change that price at all. Because a tighter string returns fewer irrelevant rows per page, precise operators usually lower total spend rather than raising it, which is the practical argument for learning the engagement gates before you start paging anything.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.