# Twitter X Reply Scraper By Verified & Engagement Filter (`api-empire/twitter-x-reply-scraper`) Actor

Twitter X Reply Scraper: Extract replies and filter by verified status and engagement metrics. Collect usernames, profile details, reply text, timestamps, likes, reposts, views, and URLs to analyze influential conversations, audience responses, and engagement trends.

- **URL**: https://apify.com/api-empire/twitter-x-reply-scraper.md
- **Developed by:** [API Empire](https://apify.com/api-empire) (community)
- **Categories:** Social media, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.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 Reply Scraper — Posts, Replies and Engagement Filters

Twitter X Reply Scraper turns an X (Twitter) **post link, profile link or search link** into typed JSON rows: the matching **posts** and the **replies** underneath each one — pre-filtered to verified repliers, a minimum like count, and keyword matches before anything is saved. Every row is normalized JSON — no HTML, no selectors, no parsing step between the Actor and your pipeline. Post and profile links work with **no login at all**; search links need your own X session, because X answers its search endpoint only for logged-in callers. What follows documents every filter formula, every field the Actor actually writes, and exactly which parts of X are reachable with and without a session.

### What is Twitter X Reply Scraper By Verified & Engagement Filter?

Twitter X Reply Scraper By Verified & Engagement Filter is an Apify Actor that collects X **posts** and their **replies**, filtered down before anything is written to your dataset. It talks to the same endpoints x.com's own web client uses, and it picks one of two transports automatically depending on whether you supplied a session.

| Mode | What you supply | What works | Replies per post |
| ----- | ----- | ----- | ----- |
| **Public mode** (default) | Nothing | Post links, profile links | Up to **3** — X's own logged-out limit |
| **Session mode** | `authToken` + `ct0` cookies from your own X account | Post links, profile links **and search links** | Every reply X's `TweetDetail` endpoint returns |

**Why the split exists.** X gates its GraphQL `SearchTimeline` and `TweetDetail` operations behind a login — an anonymous caller gets HTTP 404, not a partial result. What X still serves anonymously is the guest-token `TweetResultByRestId` operation (full post + author object, including verification and engagement counts) and the server-rendered post page, which includes the conversation's top 3 replies. Public mode is built on exactly those two, so it returns real, live, complete records — just capped at 3 replies per post, with no cursor to page past it. There is no third-party proxy for X search: if you need search, you need a session.

- Scrape **posts** from a profile, a single post link, or an X search query — text, author details, engagement counts, view counts
- Scrape each post's **replies**, capped per post by `maxReplies`
- Filter replies to verified repliers, a minimum like count, and/or include or exclude keywords — every active filter is combined with AND
- Export as JSON, CSV or Excel through the Apify dataset — no proxy management, no HTML parsing

### What data does Twitter X Reply Scraper collect?

Every dataset row combines the post, the replies that survived your filters, and accounting fields for what got dropped.

| Data Type | Key Fields | JSON Field Names |
| ----- | ----- | ----- |
| Posts (tweets) | Link, full text, ISO date, author handle/name/avatar, three verification signals, reply/retweet/quote/like/view counts | `tweetLink`, `tweetContent`, `tweetDate`, `handle`, `fullname`, `avatar`, `verified`, `isBlueVerified`, `isVerifiedOrganization`, `verifiedType`, `commentCount`, `retweetCount`, `quoteCount`, `likeCount`, `viewCount` |
| Replies (nested per post) | Same field shape as a post, one object per surviving reply, capped at `maxReplies` | `repliesData[].tweetLink`, `repliesData[].tweetContent`, `repliesData[].handle`, `repliesData[].fullname`, `repliesData[].avatar`, `repliesData[].verified`, `repliesData[].isBlueVerified`, `repliesData[].verifiedType`, `repliesData[].tweetDate`, `repliesData[].commentCount`, `repliesData[].retweetCount`, `repliesData[].quoteCount`, `repliesData[].likeCount`, `repliesData[].viewCount` |
| Reply accounting | How many replies were fetched for that post, and how many your filters removed | `repliesFetched`, `repliesFilteredOut` |

#### Need more X data?

Twitter X Reply Scraper By Verified & Engagement Filter is built specifically around post → reply threads with verified and engagement filtering applied before rows are saved. If you need the posts themselves without the reply-filtering layer — media and link extraction across a broader result set — **Twitter X Posts Scraper (Media & Link Extraction)** covers that ground. For trend discovery instead of a link-driven collection, **Twitter Trends Scraper & Country List Per Trend** surfaces what's trending and where.

### How does Twitter X Reply Scraper differ from the official X API?

X publishes its own X API (`developer.x.com`) for programmatic access to posts, search and conversation data. Getting there means registering a developer account, creating an approved App, and picking one of X's paid API access tiers — the tier you're granted determines which endpoints, including search and reply/conversation retrieval, you're allowed to call. Twitter X Reply Scraper needs none of that: paste a link and press Start.

| Feature | Official X API | Twitter X Reply Scraper |
| ----- | ----- | ----- |
| Access | Developer account, approved App, a paid X API tier | ✅ Apify account. No developer registration, no API key |
| Post + profile scraping | Governed by your tier | ✅ Works with no credentials at all |
| Search | Governed by your tier | Needs your own X session cookies (`authToken` + `ct0`) |
| Reply/conversation retrieval | Governed by your tier | 3 per post anonymously; every reply `TweetDetail` returns with a session |
| Reply-level filtering | Not part of the API surface — build it yourself | ✅ Verified-only, minimum likes, include/exclude keywords, applied before rows are saved |
| Output shape | X's documented, versioned schema | One flat row per post, replies nested under `repliesData` |

### Why do developers and teams scrape X replies?

X's reply threads are where intent, sentiment and sourcing signals live — not in the original post.

#### For journalists and researchers sourcing quotes

Point the Actor at a post asking for input, or at an account that posts calls for sources, and read only the replies worth a follow-up. Set `verifiedRepliersOnly` if you only want replies from accounts showing a check, or use `replyKeywords` to keep replies that mention `"expert"` or `"happy to talk"`. Because filtering runs before the row is saved, a thread with 200 replies and 6 usable ones costs one dataset row, not 200.

#### For marketers and brand teams

A brand account's profile link turns into a live read on how people are engaging, not just whether they posted. Use `minReplyLikes` to surface replies the audience itself has already amplified, and `excludeKeywords` to drop spam and giveaway noise before it reaches your dashboard.

#### For sales and community teams

Reply text is a lead signal when someone answers a call-for-interest post with words like `"available"` or `"pitch me"`. Set `replyKeywords` to the phrases your offer expects and `verifiedRepliersOnly` to prioritise established accounts.

#### For AI engineers and agent builders

Typed JSON with a stable field set means a reply thread can be indexed into a vector store or passed straight into an LLM context window without an HTML-stripping step. Cap `maxReplies`, and hand `tweetContent` plus the nested `repliesData` array to a summarisation or sentiment-scoring agent.

### How to scrape X replies (step by step)

1. Open **Twitter X Reply Scraper By Verified & Engagement Filter** on the Apify Store and click **Try for free**
2. Paste one or more links into **🔗 Start URLs** (`startUrls`) — this is the only required input:
   - a post link (`https://x.com/nasa/status/2083296052283613303`)
   - a profile link (`https://x.com/nasa`)
   - a search link (`https://x.com/search?q=...`) — session required
3. Optionally add your X session under **🔐 Account session** — `authToken` and `ct0`, or the `AUTH_TOKEN` / `CT0` environment variables. Leave it empty to run in public mode
4. Set your reply filters under **🔎 Reply filters** — `verifiedRepliersOnly`, `minReplyLikes`, `replyKeywords`, `excludeKeywords` — and set `maxSearchResults` / `maxReplies` to size the run
5. Click **Start**, then read results live in the dataset tab as each post is saved

The log states which mode the run is in on its first lines, then reports each post as it is saved: handle, how many replies were kept out of how many fetched, and a preview of the post text. Multiple `startUrls` are processed one after another, each with its own `maxSearchResults`/`maxReplies` budget; the same filter settings apply to every link in the run.

#### Handling a search link with no session

If you pass a search link in public mode, the Actor does not pretend. It logs an explicit error telling you the session is missing, saves nothing for that link, and continues to the next one. The single exception: a query of the form `from:someaccount some terms` is degraded to that account's recent public posts with the remaining terms applied locally as a text filter — the log says so, in as many words, every time it happens.

#### What to do when X changes its structure

The Actor doesn't hardcode X's GraphQL query IDs. Each run re-extracts the current `SearchTimeline`, `TweetDetail` and `TweetResultByRestId` query IDs from x.com's live `main.*.js` bundle, falling back to bundled IDs only if that fails. Feature flags are self-healing too: when X starts requiring a flag the Actor doesn't send, the resulting HTTP 400 is parsed, the flags are added, and the request is retried automatically. The `x-client-transaction-id` header is generated per request from X's own transaction-signing algorithm.

### What changed in X scraping recently?

Three structural shifts shape how this Actor — and any X scraper — has to work.

1. **The login wall (2023 onward).** Anonymous search stopped working, and it still does not work: X returns HTTP 404 for `SearchTimeline` and `TweetDetail` without a session. What survives anonymously is single-post lookup and the 3 replies x.com server-renders on a logged-out post page.
2. **Verification became a purchase (2023).** X retired the legacy free blue checkmark and folded verification into the paid **X Premium** subscription. A check on X today means a paying subscriber or an eligible organisation — not an identity check. See the section below for exactly which field this Actor reads.
3. **x.com's front end was rebuilt.** The root URL now serves a different logged-out shell than in-app routes do, which is why the Actor probes `/home` and `/search` for the JS bundle rather than the root.

### ⬇️ Input

Ten parameters, read directly from the Actor's input schema. Only `startUrls` is required.

| Parameter | Required | Type | Description | Example Value |
| ----- | ----- | ----- | ----- | ----- |
| `startUrls` | ✅ Yes | array | One or more X links: post (`/status/<id>`), profile (`x.com/<handle>`), or search (`x.com/search?q=…`, session required). `editor: stringList`. | `["https://x.com/NASA"]` |
| `maxSearchResults` | No | integer | Hard cap on posts collected per link. A post link always yields 1. Minimum `1`, maximum `5000`, default `10`. | `25` |
| `maxReplies` | No | integer | Hard cap on replies kept per post, counted **after** the reply filters. Minimum `1`, maximum `500`, default `5`. In public mode X itself supplies at most 3. | `15` |
| `verifiedRepliersOnly` | No | boolean | Keep only replies from an account showing a check on X. Unverified replies are dropped before `maxReplies` is applied. Default `false`. | `true` |
| `minReplyLikes` | No | integer | Drop any reply with fewer likes (favorites) than this number. `0` disables the floor. Minimum `0`, maximum `10000000`, default `0`. | `5` |
| `replyKeywords` | No | array | Keep only replies whose text contains at least one of these terms (case-insensitive substring). Default `[]` — no include filtering. | `["pitch", "available"]` |
| `excludeKeywords` | No | array | Drop any reply whose text contains one of these terms, applied after the include filter. Default `[]` — disabled. | `["spam", "giveaway"]` |
| `authToken` | No | string | Optional. Your own X `auth_token` cookie, or the `AUTH_TOKEN` environment variable. Unlocks search links and full reply threads. Stored as an encrypted Apify secret. | `"<YOUR_X_AUTH_TOKEN_COOKIE>"` |
| `ct0` | No | string | Optional. The companion `ct0` (CSRF) cookie, or the `CT0` environment variable. Both must be present for session mode. | `"<YOUR_X_CT0_COOKIE>"` |
| `proxyConfiguration` | No | object | Apify Proxy settings. `editor: proxy`, defaults to `{"useApifyProxy": true}` — recommended, since X rate-limits per IP. Setting it to `{"useApifyProxy": false}` runs direct and is fully supported. | `{"useApifyProxy": true}` |

**How the reply filters really combine.** All active filters are ANDed — a reply must pass every filter you turn on. They run locally against fields already fetched, so turning more of them on never costs an extra request. `maxReplies` is applied **last**, after filtering, so it caps how many *surviving* replies you keep.

**A missing session is not an error by itself.** With no `authToken`/`ct0` the run proceeds in public mode and returns real data for post and profile links. Only search links require the session, and only those are refused.

#### Example input

```json
{
  "startUrls": [
    "https://x.com/NASA",
    "https://x.com/NASA/status/2083296052283613303"
  ],
  "maxSearchResults": 25,
  "maxReplies": 15,
  "verifiedRepliersOnly": true,
  "minReplyLikes": 5,
  "replyKeywords": ["eclipse"],
  "excludeKeywords": ["giveaway"],
  "proxyConfiguration": { "useApifyProxy": true }
}
```

### ⬆️ Output

One dataset row per post, with its filtered replies nested inside it. Fields are typed and consistently named across runs.

#### Scraped post (tweet) row

```json
{
  "tweetLink": "https://x.com/NASA/status/2083296052283613303",
  "avatar": "https://pbs.twimg.com/profile_images/1321163587679784960/0ZxKlaBJ_bigger.jpg",
  "fullname": "NASA",
  "handle": "@NASA",
  "verified": true,
  "isBlueVerified": true,
  "isVerifiedOrganization": true,
  "verifiedType": "Government",
  "tweetDate": "2026-07-31T18:00:00.000Z",
  "tweetContent": "The sky is full of excitement this month!…",
  "commentCount": 214,
  "retweetCount": 611,
  "quoteCount": 42,
  "likeCount": 5176,
  "viewCount": 486213,
  "repliesFetched": 3,
  "repliesFilteredOut": 1,
  "repliesData": [
    "… see the reply object shape below, one entry per surviving reply, capped at maxReplies …"
  ]
}
```

`repliesFetched` is always present — it is how many replies X returned for that post before any filtering. `repliesFilteredOut` is present **only** when at least one reply filter is active, and counts replies your filters actively removed (it does **not** include replies trimmed by the `maxReplies` cap; compare `repliesFetched`, `repliesFilteredOut` and `repliesData.length` if you need that breakdown).

`avatar` is always the higher-resolution version of the profile image — the Actor rewrites X's `_normal` suffix to `_bigger` on both posts and replies. `tweetDate` is normalized to ISO 8601 (`YYYY-MM-DDTHH:MM:SS.000Z`); if X's own date string ever fails to parse, the raw string is saved as-is rather than dropping the field. `viewCount` is `null` when X does not expose a view count for that post.

#### Reply object shape (inside `repliesData`)

```json
{
  "tweetLink": "https://x.com/spacecoin/status/2083309572521472147",
  "avatar": "https://pbs.twimg.com/profile_images/%E2%80%A6_bigger.jpg",
  "fullname": "Spacecoin",
  "handle": "@spacecoin",
  "verified": true,
  "isBlueVerified": true,
  "isVerifiedOrganization": true,
  "verifiedType": "Business",
  "tweetDate": "2026-07-31T18:52:11.000Z",
  "tweetContent": "Can't wait to catch that lunar eclipse!",
  "commentCount": 0,
  "retweetCount": 1,
  "quoteCount": 0,
  "likeCount": 5,
  "viewCount": 1204
}
```

`verified` is a real boolean on every post and every reply — `true` or `false`, never `null`.

### ⚠️ How verified and engagement filtering actually work

This is the core of the Actor, so it is worth stating exactly.

**Verified-repliers detection — and what "verified" means on X today.** X exposes three separate signals on a user object, and they mean different things:

| Field | What it means today |
| ----- | ----- |
| `is_blue_verified` | The account pays for X Premium (ex Twitter Blue). Purchasable by anyone. This is what the overwhelming majority of checks on X are. |
| `verification.verified_type` | `Business` or `Government` — a verified organisation or official account. Surfaced as `verifiedType`, with `isVerifiedOrganization: true`. |
| `verification.verified` (legacy `verified`) | The retired pre-2023 identity check. It is `false` on virtually every live account, including accounts that visibly show a check. **A scraper that filters on this field alone returns nothing.** |

The Actor's `verified` output field is the union of all three — "this account shows a check on X" — and `verifiedRepliersOnly` keeps a reply only when `verified` is exactly `true`. `isBlueVerified`, `isVerifiedOrganization` and `verifiedType` are written alongside it so you can narrow further downstream: filter `verifiedType === "Government"` for official accounts only, for example. Understand what you are selecting for: a check is a paid subscription, not an identity guarantee.

**Engagement filter (`minReplyLikes`).** A reply is kept when its `likeCount` — X's `favorite_count` at fetch time — is greater than or equal to `minReplyLikes`. `0` disables the floor. There is no combined "engagement score"; the engagement filter is a like-count threshold only.

**Keyword filters.** `replyKeywords` is an allow-list: if set, a reply is kept only when its text contains at least one term, case-insensitive, substring match. `excludeKeywords` is a block-list checked afterward. Both are empty by default.

**Order and combination.** All active filters are combined with AND, checked in this order: verified → minimum likes → include keywords → exclude keywords. `maxReplies` is applied last, as a hard slice.

**Billing is per post, not per reply.** Every post is one row, regardless of how many replies survive filtering — a post whose replies were all filtered out still pushes a row with `repliesData: []`.

**Reply-fetch failures look like "no replies."** A failed reply fetch for a specific post is treated the same as a post genuinely having zero replies, and the row is still saved with `repliesData: []` and `repliesFetched: 0`. If a session-mode run starts returning `repliesFetched: 0` everywhere, check the log — the Actor logs a warning and switches to public replies when X rejects the logged-in reply request.

### How does Twitter X Reply Scraper compare to other X reply scrapers?

| Feature | Twitter X Reply Scraper | Generic alternative |
| ----- | ----- | ----- |
| Runs with no credentials | ✅ Post and profile links, no login | Usually requires cookies or a paid API key |
| Output format | ✅ Typed JSON, fixed keys on every row | Varies by actor |
| Reply-level filtering before save | ✅ Verified / minimum likes / include & exclude keywords, ANDed | Not commonly offered as a pre-save filter |
| Verification detail | ✅ `verified`, `isBlueVerified`, `isVerifiedOrganization`, `verifiedType` | Usually a single ambiguous boolean |
| Entity coverage per row | Post and its nested, filtered replies together | Often posts and replies as separate outputs |

Observed on the Apify Store on 2026-07-25: `kaitoeasyapi/twitter-reply` takes a `conversation_ids` array plus `max_items_per_conversation` and returns tweet-level objects — its listing does not document a reply-level verified-only or minimum-like filter, and pricing is advertised at $0.25 per 1,000 tweets on that listing. Its throughput and uptime figures ("150 tweets per second", "99.9% uptime") are that competitor's own marketing claims, checked on the Apify Store, and are not measured or reproduced here. A third ranked competitor, `fastcrawler/twitter-reply-scraper-0-2-1k-tweets-pay-per-result-2025`, returned a 404 on the Apify Store as of 2026-07-25 and could not be reviewed.

### How many results can you scrape with Twitter X Reply Scraper?

`maxSearchResults` caps posts collected **per link** — minimum `1`, maximum `5000`, default `10`. `maxReplies` caps replies **kept per post, after filtering** — minimum `1`, maximum `500`, default `5`. Both are hard-clamped in code and applied as exact slices.

The practical ceilings differ by mode:

- **Public mode, profile link** — up to about 100 recent posts per account (X's syndication timeline), falling back to the 5 posts the logged-out profile page renders if that endpoint rate-limits the IP.
- **Public mode, replies** — 3 per post. X exposes no cursor for logged-out reply pagination, so this is not a setting that can be raised.
- **Session mode, search link** — pages through X's own search cursor 20 posts at a time until `maxSearchResults` is reached or the cursor runs out.

X rate-limits per IP on every one of these surfaces, which is why `proxyConfiguration` defaults to Apify Proxy. No throughput benchmark is quoted here because none has been measured for this documentation.

### Integrate Twitter X Reply Scraper and automate your workflow

#### REST API integration

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("<YOUR_USERNAME>/twitter-x-reply-scraper-by-verified-engagement-filter").call(run_input={
    "startUrls": ["https://x.com/NASA"],
    "maxSearchResults": 10,
    "maxReplies": 5,
    "verifiedRepliersOnly": True,
    "minReplyLikes": 5,
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["handle"], row["tweetLink"], len(row["repliesData"]))
```

Works in Python, Node.js, Go, Ruby, cURL.

#### Automation platforms (n8n, Make, LangChain)

In **n8n**, use the Apify node — or an HTTP Request node against the Actor's run endpoint — with the JSON input shown above; a downstream Filter node can branch on `verified` or on `repliesFilteredOut`. In **Make**, the Apify module's run-and-wait mode lets you schedule a recurring collection and pipe rows straight into Google Sheets, Airtable or a Slack notification step. In **LangChain**, wrap the Actor run as a Tool and hand `repliesData` directly to the model — it's already typed JSON.

### Is it legal to scrape X replies?

Yes, when limited to publicly accessible posts and replies, though the data involved is personal data and that changes what applies. This Actor returns only posts and replies visible to a normal visitor — in public mode, literally what a logged-out browser is served; protected/locked accounts and private conversations are not reachable.

Because a post's `handle`, `fullname`, `avatar` and reply text identify real individuals, this falls under personal-data regimes like GDPR and CCPA where they apply — you need a lawful basis to store and use it. Consult legal counsel for commercial use cases involving bulk personal data.

### ❓ Frequently asked questions

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

Yes, for post links and profile links — that is public mode, and it is the default. You get the full post record and up to 3 replies per post, which is all X server-renders for logged-out visitors. Search links are the exception: X refuses search to anonymous callers, so those need `authToken` + `ct0` from your own account.

#### Why only 3 replies per post without a session?

Because that is what X returns. The logged-out post page server-renders exactly three replies and provides no cursor to load more, and the GraphQL `TweetDetail` operation that would return the full thread answers anonymous callers with HTTP 404. No amount of proxying changes that; only a session does.

#### How often is the scraped data updated?

Every run fetches live from X — nothing is cached between runs. Schedule the Actor on Apify if you want a recurring refresh.

#### What happens when a post has no replies, or every reply gets filtered out?

The row is still saved. `repliesData` comes back as `[]`, `repliesFetched` reports how many replies were retrieved, and if any filter is active, `repliesFilteredOut` reports how many the filters removed.

#### Can I scrape private or protected X accounts, or direct messages?

No. Only posts and replies visible to a normal visitor are returned. Protected (locked) accounts and direct messages are not accessible, and there is no input that changes that.

#### Do I need X Premium (a paid, verified account) to use this Actor?

No. Public mode needs no account at all, and a session used for search mode doesn't need to be verified or paid — `verifiedRepliersOnly` filters on the *repliers'* verification status.

#### Can I run several links, or apply different filters to each?

You can run as many links as you need in `startUrls`, each processed independently with its own `maxSearchResults`/`maxReplies` budget. You cannot apply different reply filters per link in a single run: `verifiedRepliersOnly`, `minReplyLikes`, `replyKeywords` and `excludeKeywords` are set once and apply to every link. Start separate runs if different links need different filter combinations.

#### Does it work for AI agent workflows and LLM pipelines?

Yes. It's callable as a standard HTTP-triggered Actor run through the Apify API, so LangChain, CrewAI, n8n, or a hand-written tool definition can invoke it and receive typed JSON with no parsing step.

#### How does it handle X's anti-bot system?

The Actor generates its `x-client-transaction-id` header per request using X's own transaction-signing algorithm, re-extracts current GraphQL query IDs from X's live JS bundle each run, refreshes its guest token when X rejects one, self-heals missing GraphQL feature flags from X's own 400 responses, and routes through Apify Proxy by default. If a search request is blocked while running direct, it retries once through Apify Proxy before reporting failure.

#### Can I use it without managing proxies?

Yes. `proxyConfiguration` defaults to Apify Proxy and needs no configuration. Setting `{"useApifyProxy": false}` is fully supported and runs direct from the platform IP — expect X's per-IP rate limits to bite sooner on longer runs.

### 💬 Your feedback

Found a bug, or missing a field that X's response actually carries? Open an issue on the Actor's Issues tab on Apify with the link you used and the exact input JSON (redact your session cookies first).

# Actor input Schema

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

📋 Add any mix of three X link types:

• **Post link** — `https://x.com/user/status/123…` → that post plus its replies. Works with **no login**.
• **Profile link** — `https://x.com/nasa` → that account's recent posts plus each one's replies. Works with **no login**.
• **Search link** — `https://x.com/search?q=…` → every matching post plus its replies. **Requires your X session** (`authToken` + `ct0`) because X refuses search to logged-out callers.

Add as many links as you need — they run one after another. 🌍✅

## `maxSearchResults` (type: `integer`):

🎯 How many posts to take from each link — a hard cap, never exceeded. Post links always yield one post; profile and search links yield up to this many. Higher numbers give a broader snapshot; lower numbers finish faster. ⚡📈

## `maxReplies` (type: `integer`):

💡 How many replies to keep per post — counted AFTER the filters below run, and enforced as a hard cap. Note: without an X session, X server-renders only 3 replies per post to logged-out visitors, so 3 is the real ceiling in that mode. 🙌🧵

## `verifiedRepliersOnly` (type: `boolean`):

Keep only replies written by an account that shows a check on X — an X Premium subscriber (`is_blue_verified`) or a verified organisation/government account (`verified_type`). Since 2023 that check is purchasable, so it means "paid or organisation-verified", not "identity-vetted". Unverified replies are dropped before your reply limit is applied. Default is off.

## `minReplyLikes` (type: `integer`):

Drop any reply with fewer likes (favorites) than this number. Example: minReplyLikes=5 keeps only replies with 5 or more likes. Set to 0 to keep replies with any like count. Default is 0.

## `replyKeywords` (type: `array`):

Keep only replies whose text contains at least one of these words or phrases (case-insensitive substring match). Example: \["pitch", "available"] keeps replies mentioning either. Leave empty to keep replies regardless of wording.

## `excludeKeywords` (type: `array`):

Drop any reply whose text contains one of these words or phrases (case-insensitive substring match). Example: \["spam", "giveaway"] removes replies mentioning either. Leave empty to disable. Applied after the include filter.

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

🔑 Optional. Your own X account's `auth_token` cookie (or set the AUTH\_TOKEN environment variable). Leave empty and the Actor runs in public mode: post and profile links still work, capped at 3 replies per post. Fill it in (together with ct0) to unlock X **search** links and full reply threads. Keep it private — never share it publicly! 🤫🔒

## `ct0` (type: `string`):

✨ Optional. The companion `ct0` (CSRF) cookie for your X session, or use the CT0 env var. Both authToken and ct0 must be present for session mode — one without the other stays in public mode. 🔐💙

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

🌍 **Apify Proxy** settings. Recommended on: X rate-limits per IP, and rotating datacenter IPs keeps longer runs healthy. Turn it off to go out from the platform's own IP. ☁️✅

## Actor input object example

```json
{
  "startUrls": [
    "https://x.com/NASA"
  ],
  "maxSearchResults": 10,
  "maxReplies": 5,
  "verifiedRepliersOnly": false,
  "minReplyLikes": 0,
  "replyKeywords": [],
  "excludeKeywords": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped items in the Actor's default dataset.

# 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": [
        "https://x.com/NASA"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("api-empire/twitter-x-reply-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": ["https://x.com/NASA"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("api-empire/twitter-x-reply-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": [
    "https://x.com/NASA"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call api-empire/twitter-x-reply-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,api-empire/twitter-x-reply-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/AEhmQvtChuZZANpS7/builds/wr1aQC4YClsuu2s3M/openapi.json
