# Twitter X Posts Scraper By Keyword & Hashtag Search (`scrapelabsapi/twitter-x-posts-scraper`) Actor

Twitter X Posts Scraper By Keyword & Hashtag Search collects posts from X with text, timestamps, likes, reposts, replies, hashtags & media links. Fast, reliable, and scalable for social listening, competitor tracking, sentiment and marketing research.

- **URL**: https://apify.com/scrapelabsapi/twitter-x-posts-scraper.md
- **Developed by:** [ScrapeLabs](https://apify.com/scrapelabsapi) (community)
- **Categories:** Automation, Lead generation, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### Twitter X Scraper — Extract Posts, Author Profiles and Quotes

Twitter X Posts Scraper By Keyword & Hashtag Search turns any keyword, phrase, or `#hashtag` into a live X (Twitter) search and returns every matching post as structured JSON — the post itself, the full author profile behind it, and the quoted post if the match is a quote-tweet. Unlike scraping frameworks that return raw HTML, this Actor returns typed JSON ready for your database, your pipeline, or your model's context window without any parsing. Each dataset row carries 27 fields: engagement metrics, author details, hashtags, mentions, media links, and the quoted post, all under the exact query that produced it. The guide below covers every input and output field and how teams run this Actor for monitoring, enrichment, and dataset-building workloads.

### What Does the Twitter X Scraper Do?

Twitter X Posts Scraper By Keyword & Hashtag Search runs one live search per line you provide against X's internal `SearchTimeline` engine — the same endpoint the X web app uses when you type a query into the search box. It supports X's advanced-search operators (minimum likes, date range, language, verified-only) composed automatically from structured inputs, and returns each matching post together with the posting account's public profile and, where the match is a quote-tweet, the quoted post's own details. Because X's search feature is login-gated, the Actor requires your own X session cookies (`auth_token` and `ct0`) — there is no anonymous or guest-search path.

- Searches by keyword, phrase, or hashtag — one line, one live query
- Chooses between `latest` (reverse-chronological) and `top` (X's relevance ranking) result feeds
- Composes `min_faves`, `since`, `until`, `lang`, and `filter:verified` operators from plain inputs
- Returns the full posting account's profile on every row — no separate profile lookup needed
- Unwraps quote-tweets into a nested `quoted_post` object
- Paginates via X's own search cursor up to your configured cap per term
- Auto-escalates to a residential proxy if the session hits an authentication wall

### Features & Capabilities

The Actor's feature set covers query composition, per-row profile enrichment, and resilience to X's own anti-automation measures.

#### Core features

- **Keyword and hashtag search** — `startUrls` accepts one search term per line (plain keyword, quoted phrase, `#hashtag`, or a raw X search string like `from:nasa mars`)
- **Two search feeds** — `searchMode: latest` for reverse-chronological results in a recent window, or `searchMode: top` for X's ranked most-relevant results
- **Advanced-search filters composed for you** — `minLikes` → `min_faves:<n>`, `dateFrom` → `since:YYYY-MM-DD`, `dateTo` → `until:YYYY-MM-DD`, `language` → `lang:<code>`, `verifiedOnly` → `filter:verified`
- **Author profile on every row** — `user_posted`, `name`, `biography`, `followers`, `following`, `posts_count`, `profile_image_link`, `is_verified` are returned inline with the post, so there is no second lookup to join
- **Quote-tweet unwrapping** — a nested `quoted_post` object carries the quoted post's text, id, author id, author name, and posting date whenever the matched post is a quote
- **Configurable row ordering** — `sortOrder` sorts each term's collected rows by recency or by like count before they're saved
- **Live credential discovery** — the Actor reads X's current web-app bearer token and `SearchTimeline` query id directly out of X's own JavaScript bundle before each run, instead of relying on a hardcoded value that goes stale when X ships a new client build

#### How Twitter X Scraper compares to other X search scrapers

| Feature | This Actor | scraper\_one/x-posts-search |
| --- | --- | --- |
| Output format | Typed JSON, 27-field row | Typed JSON, flat post + nested `author` object |
| Author profile fields per row | 8 fields (followers, following, bio, verified, avatar, post count) | 4 fields (`followersCount`, `favouritesCount`, `friendsCount`, `description`) |
| Advanced-search operators | `minLikes`, `dateFrom`, `dateTo`, `language`, `verifiedOnly` — all composed automatically | Not documented — only a time-window filter (`timeWindow` / `timeWindowHours`) |
| Quote-tweet data | Nested `quoted_post` object | Not documented |
| Login requirement | Requires your own X session cookies (`authToken`, `csrfToken`) | Not documented as a required input — no cookie field appears in their published input parameters |
| Proxy / anti-bot handling | Residential proxy auto-escalation on auth-wall responses; live bearer/query-id discovery | Not documented |

*As observed on the scraper\_one/x-posts-search Apify Store listing, checked 26 July 2026.*

If your use case is feeding structured post data straight into an LLM or an agent tool, the output-format row above is the decision-maker — parsing an `author` object that only carries four fields means a second lookup for anything else, while this Actor's row already carries the full profile.

#### When another tool might suit you better

If you don't need advanced-search operators or the full author profile on every row, and you just want a fast keyword lookup with minimal fields, scraper\_one/x-posts-search's flatter schema may be a simpler fit for a quick one-off pull. If your monitoring target is Meta Threads or LinkedIn rather than X, neither of those platforms is covered here — see Related Scrapers below for the account's equivalent tools on those platforms.

#### Twitter X Scraper within the Scrapio data stack

Twitter X Posts Scraper By Keyword & Hashtag Search covers keyword and hashtag search results. For a single account's own timeline, use X Twitter Posts Search With Author Profile Details. For monitoring trending topics and boards rather than a fixed keyword list, use Twitter Trends Scraper - Brand & Keyword Monitoring. For reply-level sentiment on posts you've already found, use Twitter X Reply Scraper: Reply Sentiment & Tone Breakdown.

### Why Do Developers and Data Teams Scrape X by Keyword and Hashtag?

Different teams pull the same search results for different reasons — brand monitoring, dataset building, and research all start from the same `SearchTimeline` query but use different fields downstream.

#### Brand and campaign monitoring

A marketing or community team watches a brand name, product hashtag, or campaign tag as a scheduled search, reading `likes`, `reposts`, `replies`, and `quotes` off every new row to spot a post that's outperforming the rest. `is_verified` and `followers` on the author profile let the team prioritize which mentions come from an account worth a direct reply versus background noise, and `date_posted` orders the feed for a daily digest.

#### AI training data and RAG indexing

The `description` field (the post's full text) is the primary content field for both RAG and training use — it's the only field carrying free text of meaningful length. For RAG enrichment, indexing `description` alongside `hashtags` and `searchQuery` lets a retrieval system answer "what is being said about X topic" with the exact matched text and the query that surfaced it. For training data, the structured fields — `likes`, `replies`, `reposts`, `quotes`, `views`, `is_verified` — return as consistent typed primitives across every row, so they can be used as features without a normalization pass.

#### Competitive and market intelligence

Tracking a competitor's brand or product hashtag over scheduled runs surfaces `reposts` and `quotes` growth on their posts as an early read on which of their announcements are gaining traction, while `verifiedOnly` narrows a search to posts from verified accounts when the intelligence use case cares about official or high-profile commentary specifically.

#### Research and academic use

Social and market researchers use keyword search to build a dataset of public discourse on a topic, hashtag campaign, or event as it unfolds. The Actor returns only what X's own search surfaces to a logged-in user — no private accounts, no follower-only posts, no data behind a further permission gate. This scope matches what a human researcher browsing X's search page would see.

#### Product and SaaS development

Teams building a social-listening dashboard, an alerting tool, or a content-discovery feature on top of X data use this Actor as the ingestion layer, scheduling recurring searches per tracked term and writing new rows into their own storage as they arrive.

### ⬇️ Input Parameters

All parameters below are read directly from `.actor/actor.json`'s input schema — same names, types, and defaults as the Actor implements.

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `startUrls` | Yes | array | One keyword, phrase, or `#hashtag` per line. Each line is run as its own live X search. | `["#nasa", "openai", "\"climate change\"", "from:nasa mars"]` |
| `searchMode` | No | string (select) | Which `SearchTimeline` feed to pull. `latest` = reverse-chronological (recent ~7–10 day window). `top` = X's ranked most-relevant results. Default `latest`. Enum: `latest`, `top`. | `"latest"` |
| `minLikes` | No | integer | Only return posts with at least this many likes. Composed as `min_faves:<n>`. Minimum `0`. Default `0` (disabled). | `50` |
| `dateFrom` | No | string | Only posts on/after this date. Composed as `since:YYYY-MM-DD`. | `"2026-07-01"` |
| `dateTo` | No | string | Only posts before this date. Composed as `until:YYYY-MM-DD`. | `"2026-07-22"` |
| `language` | No | string | Restrict results to one language via `lang:<code>` (ISO 639-1, e.g. `en`, `es`, `ja`). Leave blank for any language. | `"en"` |
| `verifiedOnly` | No | boolean | When on, appends `filter:verified` so only posts from verified accounts are returned. Default `false`. | `false` |
| `sortOrder` | No | string (select) | How to order the collected posts before saving. `recent` = newest first, `oldest` = oldest first, `popular` = most likes first. Default `recent`. Enum: `recent`, `popular`, `oldest`. | `"recent"` |
| `maxTweets` | No | integer | Upper limit of matching posts to collect per search term. Minimum `1`, maximum `100`. Default `10`. | `50` |
| `authToken` | Yes | string (secret) | Your X `auth_token` session cookie. Search is login-gated on X, so this is required. Stored encrypted; never logged. | `"3a1b2c..."` |
| `csrfToken` | Yes | string (secret) | Your X `ct0` cookie (the CSRF token that pairs with `auth_token`). Required for search. Stored encrypted; never logged. | `"9f8e7d..."` |
| `proxyConfiguration` | No | object (proxy) | Apify proxy settings. RESIDENTIAL is strongly recommended — datacenter IPs are blocked for X search. Prefilled with `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}`. The Actor auto-escalates to residential if the session hits an auth wall. | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` |

Both `authToken` and `csrfToken` are marked as secret input fields in the Actor's schema, so they render masked in the Apify Console and are never printed in run logs.

#### Example input

```json
{
  "startUrls": ["#GenAI", "openai agents", "\"prompt engineering\""],
  "searchMode": "latest",
  "minLikes": 25,
  "dateFrom": "2026-07-01",
  "dateTo": "2026-07-25",
  "language": "en",
  "verifiedOnly": false,
  "sortOrder": "recent",
  "maxTweets": 50,
  "authToken": "<your X auth_token cookie>",
  "csrfToken": "<your X ct0 cookie>",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

#### Supported search term formats

Despite the field name `startUrls` — kept for schema compatibility with the base Twitter X Posts Scraper family — every entry is a search term, not a URL. Three real patterns:

- **Single keyword or hashtag:** `openai` or `#nasa` — the simplest form, matched against post text and hashtags.
- **Quoted phrase:** `"climate change"` — quotes force an exact-phrase match rather than matching each word independently.
- **Raw X search syntax:** `from:nasa mars` — anything X's own advanced search accepts can be typed directly as a line; it's passed straight into the composed query alongside whatever filters you set.

Each line is searched independently and produces its own set of rows tagged with the `searchQuery` that matched it — a 3-line input produces up to three times `maxTweets` rows in a single run.

### ⬆️ Output Format

Every matched post is one dataset row with 27 keys — the same shape whether the post is a plain post, a reply, or a quote-tweet. The default dataset view surfaces all 27 keys; nothing pushed to the dataset is hidden from the view for this Actor. Export as JSON, CSV, Excel, XML, or an HTML table through the Apify Console, or read the dataset directly through the Apify API.

#### Output for a matched post

```json
{
  "id": "1948217736450123456",
  "url": "https://x.com/OpenAI/status/1948217736450123456",
  "user_posted": "OpenAI",
  "name": "OpenAI",
  "description": "Announcing a new update to our agent tooling — full notes in the thread. #GenAI",
  "date_posted": "2026-07-20T14:32:07.000Z",
  "searchQuery": "#GenAI min_faves:25 since:2026-07-01 until:2026-07-25 lang:en",
  "searchMode": "latest",
  "likes": 4210,
  "replies": 312,
  "reposts": 986,
  "quotes": 145,
  "views": "812400",
  "bookmarks": 601,
  "is_verified": true,
  "followers": 3812940,
  "following": 210,
  "posts_count": 4820,
  "profile_image_link": "https://pbs.twimg.com/profile_images/example/avatar_400x400.jpg",
  "biography": "Building safe artificial general intelligence.",
  "hashtags": ["GenAI"],
  "tagged_users": null,
  "photos": null,
  "videos": null,
  "quoted_post": {
    "data_posted": null,
    "description": null,
    "post_id": null,
    "profile_id": null,
    "profile_name": null,
    "url": null,
    "videos": null
  },
  "external_url": "https://openai.com/",
  "input": {
    "url": "https://x.com/OpenAI/status/1948217736450123456/"
  }
}
```

**Field notes:**

- `id`, `url` — the tweet's own rest id and canonical post URL.
- `user_posted`, `name` — the author's `screen_name` (handle) and display name.
- `description` — the post's full text.
- `date_posted` — normalized to UTC ISO-8601 from X's raw timestamp format.
- `searchQuery` — the exact composed query string this row matched, including whichever `min_faves` / `since` / `until` / `lang` / `filter:verified` operators were active.
- `searchMode` — `latest` or `top`, echoing the input.
- `likes`, `replies`, `reposts`, `quotes`, `bookmarks` — engagement counters read straight from X's tweet object.
- `views` — X's view count, returned as-is (may be a numeric string).
- `is_verified` — reflects X's `is_blue_verified` flag on the author, i.e. Blue subscription checkmark status, not a legacy institutional-verification badge.
- `followers`, `following`, `posts_count` — the author's follower count, following count, and total post count at scrape time.
- `profile_image_link` — the author's avatar URL.
- `biography` — the author's bio text.
- `hashtags` — array of hashtag strings on the post (no `#`), or `null` if none.
- `tagged_users` — array of `@mention` screen names on the post, or `null` if none.
- `photos` — the first photo URL on the post if one exists, or `null`. This is a single URL, not an array, even though the name is plural.
- `videos` — array of video/GIF MP4 URLs, highest bitrate first, or `null` if the post has no video.
- `quoted_post` — populated only when the matched post is a quote-tweet.
- `external_url` — the author's website link from their profile, if set.
- `input` — an object with one key, `url`, holding the same post URL as the top-level `url` field.

#### Output for a quote-tweet

When the matched post quotes another post, `quoted_post` is populated instead of holding all-`null` values:

```json
"quoted_post": {
  "data_posted": "2026-07-19T09:14:52.000Z",
  "description": "Early results from the new agent benchmark are in.",
  "post_id": "1947890213340987654",
  "profile_id": "1234567890",
  "profile_name": "OpenAI Research",
  "url": null,
  "videos": null
}
```

Note the key `data_posted` (not `date_posted`) — this is how the Actor writes the quoted post's timestamp field, and it is intentionally documented here exactly as it appears in the output so a downstream mapping or schema doesn't silently drop it. `url` and `videos` inside `quoted_post` are not populated by this Actor even when the quoted post has them — only `data_posted`, `description`, `post_id`, `profile_id`, and `profile_name` are filled in.

#### Schema stability and export options

Field names stay stable across runs and do not change when X updates its front-end layout, because the Actor reads structured fields out of X's GraphQL response rather than parsing rendered HTML. If X restructures the `SearchTimeline` response itself, individual fields may return `null` until the Actor is updated, rather than the row failing outright. Results are available as JSON, CSV, Excel, XML, or an HTML table from the Apify Console, or through the Apify API and client libraries (Python, JavaScript) for programmatic access.

### How Twitter X Scraper Handles X's Anti-Bot and Rate Limits

Search on X is login-gated and protected by an `x-client-transaction-id` challenge, so the Actor does more than send a plain HTTP request. Before every run, it fetches X's live search page and reads the current web-app bearer token and `SearchTimeline` query id directly out of X's own JavaScript bundle — these values rotate as X ships new client builds, so discovering them live avoids the Actor going stale between deploys. A hardcoded public bearer token is used only as a last-resort fallback if that live discovery fails.

To pass X's transaction-id challenge, the Actor bootstraps a `ClientTransaction` context from X's home page and its "ondemand" JS file before issuing any search request, then generates a fresh transaction id for every `SearchTimeline` call.

If a search request comes back with HTTP 401 or 403 — X's auth-wall response — and the run started without a residential proxy, the Actor automatically escalates to an Apify RESIDENTIAL proxy session and retries once. If the session is still rejected after that, the Actor raises an error naming the likely cause: expired or checkpointed session cookies, and logs that `authToken` and `csrfToken` need refreshing. An HTTP 429 response is treated as a hard rate limit and raises immediately rather than retrying silently — there is no documented automatic backoff loop past that point, so a 429 ends the run for that search rather than being retried in place.

Within a single run, tweet ids are deduplicated globally across all search terms, not just within one term — if the same post matches two different keywords in your `startUrls` list, it is saved only once, credited to whichever term reached it first. Pagination for a given term stops early once two consecutive result pages return zero new (non-duplicate) rows, even if `maxTweets` hasn't been reached yet — so `maxTweets` is a ceiling on results, not a guarantee that you'll get that many rows back for a narrow or low-volume search term.

### 💡 Twitter X Scraper Strategy Guide

Beyond a one-off search pull, this Actor supports three recurring deployment patterns teams run it under.

#### Strategy 1: Real-time enrichment pipeline

Trigger a run whenever a new keyword or hashtag needs coverage — a new campaign tag going live, or a support ticket mentioning a product name. Feed the term into `startUrls`, run the Actor with a tight `maxTweets`, and append `likes`, `reposts`, `is_verified`, and `followers` onto the triggering record in your CRM or ticketing system so the first responder sees engagement context before replying.

#### Strategy 2: Scheduled monitoring and alerting

Set up an Apify schedule that runs the same `startUrls` list daily or hourly with `dateFrom` rolled forward to the last run's cutoff. Diff the new batch against the previous run's `id` values, and alert on any new row where `likes` or `reposts` cross a threshold, or where `is_verified` is `true` — surfacing high-signal mentions instead of routing every match to a human.

#### Strategy 3: Bulk dataset build

For a research or training dataset, list every keyword or hashtag variant you need in `startUrls` in one run — each line is processed independently — and set `sortOrder` and `maxTweets` per your sampling needs. Aggregate the dataset export (CSV or JSON) across runs by `searchQuery` to keep track of which term produced which rows. Because `maxTweets` is a per-term cap, the total row count for a run is the sum across every line in `startUrls`, up to 100 per line.

#### Strategy comparison at a glance

| Strategy | Best for | Run pattern | Output format |
| --- | --- | --- | --- |
| Real-time enrichment | Ticket or CRM records needing live engagement context | Single triggered run, tight `maxTweets` | JSON row appended to an existing record |
| Scheduled monitoring | Ongoing brand or hashtag watch | Apify schedule, `dateFrom` rolled forward each run | Diffed JSON batch, alert on threshold |
| Bulk dataset build | Research or training corpus across many terms | One run, multiple `startUrls` lines | Full CSV/JSON export, grouped by `searchQuery` |

### 🔗 Related X (Twitter) Scrapers & Tools

| Scraper Name | What it extracts |
| --- | --- |
| X Twitter Posts Search With Author Profile Details | Post search enriched with the author's full profile at no extra request cost |
| Twitter Trends Scraper - Brand & Keyword Monitoring | Live trending topics/boards, flagged against a brand keyword list |
| Twitter (X) Reply Scraper — Reply Sentiment & Tone Breakdown | Reply-level data and sentiment/tone analysis on posts you've already found |
| Threads Scraper: The All-in-One Data Engine for Meta Threads | The same keyword/hashtag search workflow, on Meta's Threads platform instead of X |
| Instagram Hashtag Analytics Scraper | Hashtag-based post/reel collection and analytics roll-up on Instagram |
| Reddit Posts Scraper — Comment Thread Analysis | Keyword and subreddit search with full comment-thread extraction on Reddit |

### How to Integrate Twitter X Scraper With Your Stack

Twitter X Posts Scraper By Keyword & Hashtag Search runs on Apify, so it works with any language or tool that can call the Apify API.

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

search_terms = ["#GenAI", "openai agents", "\"prompt engineering\""]

run = client.actor("<YOUR_USERNAME>/twitter-x-posts-scraper-by-keyword-hashtag-search").call(
    run_input={
        "startUrls": search_terms,
        "searchMode": "latest",
        "minLikes": 25,
        "sortOrder": "recent",
        "maxTweets": 50,
        "authToken": "<your X auth_token cookie>",
        "csrfToken": "<your X ct0 cookie>",
        "proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]},
    }
)

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())

import csv
with open("x_search_results.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
    writer.writeheader()
    writer.writerows(rows)

print(f"Saved {len(rows)} rows across {len(search_terms)} search terms")
```

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor('<YOUR_USERNAME>/twitter-x-posts-scraper-by-keyword-hashtag-search').call({
    startUrls: ['#GenAI', 'openai agents'],
    searchMode: 'latest',
    minLikes: 25,
    maxTweets: 50,
    authToken: '<your X auth_token cookie>',
    csrfToken: '<your X ct0 cookie>',
    proxyConfiguration: { useApifyProxy: true, apifyProxyGroups: ['RESIDENTIAL'] },
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((row) => console.log(row.user_posted, row.likes, row.description));
```

Both scripts work in Go, Ruby, PHP, or via a plain HTTP request — the Apify API is language-agnostic.

#### Async and scheduled pipelines

For recurring searches, create an Apify schedule against this Actor with your `startUrls` list saved as a task, rather than triggering a call from your own code every time. For fire-and-forget large jobs, start the run asynchronously through the API and poll the run's status or the dataset's item count instead of holding a connection open — the Actor streams rows to the dataset as each search term finishes, so partial results are readable before the full run ends.

### Who Needs Keyword and Hashtag Search on X? (Use Cases & Industries)

#### Brand and community managers

Track a product name or campaign hashtag across a scheduled run, reading `likes` and `reposts` to spot which mention is worth a reply, and `is_verified` to prioritize response order.

#### Market and competitive analysts

Watch a competitor's product hashtag or announcement keyword, diffing `reposts` and `quotes` growth across scheduled runs to catch traction before it shows up in broader press coverage.

#### AI and data engineering teams

Index `description` and `hashtags` for RAG retrieval over live social discourse, or pull the structured engagement fields as features for a downstream ranking or trend-detection model — every field arrives typed, with no HTML to strip first.

#### Researchers

Build a public-discourse dataset around a hashtag campaign or breaking event using only what X's own search interface surfaces to a logged-in user — no private accounts, no follower-only content, no additional access beyond a standard account's own search view.

#### PR and crisis monitoring teams

Run a tight `dateFrom`/`dateTo` window against a brand or executive's name during a launch or incident window, and use `verifiedOnly` to separate commentary from verified accounts from the broader volume of mentions.

### Is It Legal to Scrape X (Twitter) Posts?

Scraping publicly accessible data is broadly permitted under precedent such as *hiQ Labs v. LinkedIn* (9th Cir., 2019), which held that scraping data a website makes visible to any visitor, without circumventing an access control, is unlikely to violate the U.S. Computer Fraud and Abuse Act. This Actor's search function, however, is different from an anonymous public-page scrape: X's search feature requires a logged-in session, so this Actor operates using your own X account's session cookies (`auth_token` and `ct0`) rather than an anonymous request. That distinction matters for two separate questions.

**Terms of Service risk** is the more direct concern here: X's Terms of Service govern automated access performed through a user's own account, and using session cookies to run automated searches may fall under restrictions on bot-like or automated use. This is a contractual risk between the account holder and X, not a criminal one, but it is the operator's responsibility to review X's current Terms of Service before running automated searches at any meaningful volume, and to use an account they control and are willing to accept that risk with.

**Data protection obligations apply** because this Actor's output includes personal data — usernames, display names, bios, and follower counts tied to identifiable individuals, alongside the public post content. Anyone storing or processing this data for EU or California residents should consider GDPR and CCPA obligations around lawful basis, retention, and downstream use, even though the underlying posts and profiles are publicly visible to any logged-in X user.

Twitter X Posts Scraper By Keyword & Hashtag Search returns only data visible to a standard logged-in X account through the standard search interface. What you do with that data is your responsibility — consult legal counsel for commercial applications, especially any involving personal data or automated account activity.

### ❓ Frequently Asked Questions

#### Does Twitter X Scraper work without an X account?

No. X's search feature is login-gated, so the Actor requires your own `authToken` (X's `auth_token` cookie) and `csrfToken` (X's `ct0` cookie) as required inputs. Without both, the run stops immediately with an error rather than attempting a guest search.

#### How does Twitter X Scraper handle X's anti-scraping measures?

It discovers a current bearer token and `SearchTimeline` query id live from X's own JavaScript bundle before each run (instead of a hardcoded value that goes stale), builds a fresh `x-client-transaction-id` per request using X's own transaction-challenge scheme, and automatically escalates to a residential proxy if it hits an HTTP 401/403 auth wall on a non-residential connection.

#### Can I run Twitter X Scraper at scale without getting blocked?

`maxTweets` is capped at 100 per search term by the input schema, and pagination for a term stops early after two consecutive empty result pages. An HTTP 429 response from X is treated as a rate limit and ends that run rather than retrying automatically — there's no published uptime or throughput guarantee, so plan retries at the orchestration layer (schedule, queue, or your own retry logic) rather than assuming the Actor will absorb a rate limit silently.

#### How fresh is the data Twitter X Scraper returns?

Every run performs a live `SearchTimeline` fetch — there is no caching layer. `searchMode: latest` covers a recent window (X's own schema description states roughly 7–10 days); `searchMode: top` returns X's ranked most-relevant matches, which can include older posts if they're highly engaged.

#### Which fields work best for AI training and RAG indexing?

For RAG: `description` (the post's full text) is the primary content field, with `hashtags` and `searchQuery` as retrievable context for what the post was matched against. For training data: `likes`, `replies`, `reposts`, `quotes`, `views`, `bookmarks`, and `is_verified` are consistent typed fields across every row, requiring no normalization before use as model features.

#### Does using my own account's cookies to run automated searches carry any risk?

Yes — this is a Terms of Service consideration, not a data-access one. The Actor uses your own X account's session, so any automation restriction in X's current Terms of Service applies to that account the same way it would if you ran the searches manually at high volume. Review X's Terms of Service and use an account you're willing to accept that risk on.

#### Does Twitter X Scraper work with Claude, ChatGPT, and other AI agent tools?

Yes, as a standard HTTP endpoint through the Apify API — any agent framework that can make a request (LangChain, CrewAI, a custom tool definition, or a direct API call) can invoke a run and read back typed JSON rows with no parsing step required before passing them into an LLM context window.

#### How does Twitter X Scraper compare to other X search scrapers?

Checked on the Apify Store on 26 July 2026, scraper\_one/x-posts-search is the closest direct competitor: it returns a flatter post + `author` object (four author fields versus this Actor's eight) and documents a time-window filter rather than the full set of advanced-search operators (`minLikes`, `dateFrom`/`dateTo`, `language`, `verifiedOnly`) this Actor composes automatically, and its published input parameters don't list a login-cookie requirement. Where scraper\_one's README documents free-tier rate limiting ("Free users can make only a few requests per day"), this Actor documents its own real constraint instead: the 100-per-term cap and the fact that a 429 response ends a run rather than retrying silently.

#### What happens if my search term returns zero results?

The run completes normally with zero rows saved for that term rather than raising an error — check the Actor's run log, which prints a per-term row count (`Saved N rows for '<query>'`) so a zero-result term is visible without opening the dataset. A common cause is a `minLikes`, `dateFrom`/`dateTo`, or `language` filter narrow enough that no post in the current search window satisfies it — try loosening one filter at a time.

### ℹ️ Disclaimer

Twitter X Posts Scraper By Keyword & Hashtag Search extracts only data visible through a standard logged-in X account's own search feature. This tool is intended for lawful use cases only. Users are responsible for complying with X's Terms of Service and applicable data protection laws in their jurisdiction, and for using session credentials from an account they control.

# Actor input Schema

## `startUrls` (type: `array`):

One keyword, phrase, or #hashtag per line. Each line is run as its own live X search. Examples: `#nasa`, `openai`, `"climate change"`, `from:nasa mars`.

## `searchMode` (type: `string`):

Which SearchTimeline feed to pull. `latest` = reverse-chronological (recent ~7-10 day window). `top` = X's ranked most-relevant results.

## `minLikes` (type: `integer`):

Only return posts with at least this many likes. Composed into the query as `min_faves:<n>`. Leave 0 to disable.

## `dateFrom` (type: `string`):

Only posts on/after this date. Composed as `since:YYYY-MM-DD`. Example: `2026-07-01`.

## `dateTo` (type: `string`):

Only posts before this date. Composed as `until:YYYY-MM-DD`. Example: `2026-07-22`.

## `language` (type: `string`):

Restrict results to one language via `lang:<code>` (ISO 639-1, e.g. `en`, `es`, `ja`). Leave blank for any language.

## `verifiedOnly` (type: `boolean`):

When on, appends `filter:verified` so only posts from verified accounts are returned.

## `sortOrder` (type: `string`):

How to order the collected posts before saving. `recent` = newest first, `oldest` = oldest first, `popular` = most likes first.

## `maxTweets` (type: `integer`):

Upper limit of matching posts to collect per search term. Latest mode is capped by X to a recent window.

## `authToken` (type: `string`):

Your X `auth_token` session cookie. Search is login-gated on X, so this is required. Stored encrypted; never logged.

## `csrfToken` (type: `string`):

Your X `ct0` cookie (the CSRF token that pairs with auth\_token). Required for search. Stored encrypted; never logged.

## `proxyConfiguration` (type: `object`):

Apify proxy settings. RESIDENTIAL is strongly recommended for X search (datacenter IPs are blocked). The actor auto-escalates to residential if the session is walled.

## Actor input object example

```json
{
  "startUrls": [
    "#nasa"
  ],
  "searchMode": "latest",
  "minLikes": 0,
  "verifiedOnly": false,
  "sortOrder": "recent",
  "maxTweets": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "#nasa"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapelabsapi/twitter-x-posts-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": ["#nasa"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapelabsapi/twitter-x-posts-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "#nasa"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call scrapelabsapi/twitter-x-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapelabsapi/twitter-x-posts-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qx3ynoNkGNcoIfuVP/builds/rOCKzVcQAim2wKIe8/openapi.json
