# Twitter X Reply Scraper — Conversation Engagement Analytics (`scrapier/twitter-x-reply-scraper`) Actor

Twitter X Reply Scraper: Extract post replies, authors, timestamps, likes, reposts, views, and engagement metrics from X conversations. Analyze audience reactions, measure conversation engagement, identify trends, monitor competitors, and build structured datasets for social media research.

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

## Pricing

from $5.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 — Conversation Engagement Analytics

Twitter X Reply Scraper — Conversation Engagement Analytics collects posts from any X (Twitter) search link together with their replies, then builds a per-post **engagementReport** and a ranked **topReplies** list — both computed locally from the replies it just captured, with no extra requests and no AI. Paste one or more X search URLs, set how many posts and replies to pull, and rows stream into the dataset as typed JSON — ready to pass to an LLM, load into a spreadsheet, or feed a monitoring pipeline. Every run reads through your own X session.

### What is Twitter X Reply Scraper — Conversation Engagement Analytics?

It is an Apify Actor that turns an X search URL into a dataset of posts, their direct replies, and a deterministic engagement summary for each post. Under the hood it calls X's own `SearchTimeline` and `TweetDetail` GraphQL endpoints — the same ones x.com's web client uses — with dynamically extracted query IDs and browser-accurate request signing, so it behaves like a signed-in browser session rather than a bot fingerprint.

An X account session (`authToken` + `ct0`) is required — the run exits immediately if neither the input fields nor the `AUTH_TOKEN` / `CT0` environment variables are set. There is no anonymous mode: reading reply threads on X requires a logged-in session.

What it returns, per search link:

- **Posts** matching your search query, with author, text, timestamp and engagement counts
- **Direct replies** captured for each post, up to your configured limit
- **`engagementReport`** — reply rate, unique/repeat repliers, verified-replier share, and like/retweet/quote/view statistics, computed over exactly the replies captured
- **`topReplies`** — the most-liked captured replies for each post, ranked highest first
- **`viewCount`** on every post and reply, read from X's public impression counter when X exposes it

Query controls exposed in the input schema: how many posts to collect per search link (`maxSearchResults`), how many replies to capture per post (`maxReplies`), whether to build the engagement report (`engagementAnalytics`), and how many top replies to highlight (`topRepliesLimit`). There is no region, language or sort-order parameter — see the FAQ below for what actually controls sort order.

### What data can you get with Twitter X Reply Scraper — Conversation Engagement Analytics?

Three result types come back on every row: the post itself, its captured replies, and — when analytics are on — the computed engagement summary and top-reply ranking.

| Result Type | Extracted Fields | Primary Use Case |
| ----- | ----- | ----- |
| Post | `tweetLink`, `avatar`, `fullname`, `handle`, `verified`, `tweetDate`, `tweetContent`, `commentCount`, `retweetCount`, `quoteCount`, `likeCount`, `viewCount` | Identify and rank the posts matching your search |
| Captured replies (`repliesData`) | Same 12 fields as a post, one object per direct reply | Read the raw conversation, or feed it to your own analysis |
| Engagement report (`engagementReport`) | `repliesCaptured`, `reportedReplyCount`, `replyRate`, `uniqueRepliers`, `repeatRepliers`, `verifiedRepliers`, `verifiedReplierShare`, `sumReplyLikes`, `avgReplyLikes`, `medianReplyLikes`, `topReplyLikes`, `sumReplyRetweets`, `sumReplyQuotes`, `totalReplyViews` | Score conversation quality without reading every reply |
| Top replies (`topReplies`) | Same 12 reply fields, sorted by `likeCount` descending | Surface the highest-signal replies without manual sorting |

#### Conversation engagement analytics

`engagementReport` is deterministic local aggregation over the replies your run actually captured — no extra fetch, no model call. Every field is derived from data already in the response:

```json
{
  "engagementReport": {
    "repliesCaptured": 5,
    "reportedReplyCount": 11,
    "replyRate": 0.4545,
    "uniqueRepliers": 5,
    "repeatRepliers": 0,
    "verifiedRepliers": 2,
    "verifiedReplierShare": 0.4,
    "sumReplyLikes": 14,
    "avgReplyLikes": 2.8,
    "medianReplyLikes": 1.0,
    "topReplyLikes": 8,
    "sumReplyRetweets": 1,
    "sumReplyQuotes": 0,
    "totalReplyViews": 4213
  }
}
```

Here is exactly how each figure is computed (source: `src/analytics.py`), so nothing in the report is a mystery number:

- `repliesCaptured` — count of replies actually captured for this post (bounded by `maxReplies`)
- `reportedReplyCount` — the post's own reply count as reported by X (`commentCount`)
- `replyRate` — `repliesCaptured / reportedReplyCount`, rounded to 4 decimals; `0.0` when `reportedReplyCount` is `0`
- `uniqueRepliers` / `repeatRepliers` — distinct reply-author handles vs. `repliesCaptured − uniqueRepliers`
- `verifiedRepliers` / `verifiedReplierShare` — count and share of captured replies from a verified author
- `sumReplyLikes`, `avgReplyLikes`, `medianReplyLikes`, `topReplyLikes` — sum, mean, median and maximum of `likeCount` across captured replies (average and median rounded to 4 decimals)
- `sumReplyRetweets`, `sumReplyQuotes` — sums of `retweetCount` and `quoteCount` across captured replies
- `totalReplyViews` — sum of `viewCount` across replies that expose one; `null` when none of the captured replies has a view count (X withholds views on some tweets — this is never fabricated as `0`)

When zero replies are captured, the report is still emitted — every numeric field zeroed and `totalReplyViews` set to `null` — rather than omitted, so the schema never changes shape between rows.

#### Captured replies

`repliesData` holds the direct replies to each post, each in the same 12-field shape as a post row. Only replies whose `in_reply_to_status_id_str` matches the original post are kept — first-level replies, not a full nested thread (see the FAQ below). `topReplies` reuses this same shape, just re-sorted by `likeCount` and truncated to `topRepliesLimit`.

### Why not build this yourself?

X has no public search API that anonymous or app-only callers can use to pull an arbitrary search timeline plus reply threads — reading a reply thread requires a logged-in session, which is why `authToken` and `ct0` are required inputs here. Building that yourself means solving three moving problems before you extract a single reply:

**Query IDs rotate.** X's GraphQL endpoints are addressed by a `queryId` embedded in its own compiled JS bundle (`main.<hash>.js`), and that hash — and sometimes the ID itself — changes without notice. This Actor downloads the current bundle, extracts the `SearchTimeline` and `TweetDetail` query IDs with a set of regex patterns, caches them for 5 minutes, and falls back to a last-known-good ID if extraction fails, so a bundle change degrades gracefully instead of breaking the run.

**Requests must be signed.** X requires an `x-client-transaction-id` header generated from the page's own transaction-signing logic. This Actor builds that signature per request using the `XClientTransaction` library against X's live homepage and `ondemand.js` file, cached for an hour and invalidated whenever the proxy changes, with a static fallback ID if signing fails.

**Blocking is progressive, not binary.** A single blocked request doesn't mean the run should fail. This Actor treats HTTP 401/403/429/503, a missing status, or a GraphQL `errors` payload as a block, then escalates through direct → datacenter proxy → residential proxy, retrying the residential tier up to three times with backoff, and keeps a working residential session "sticky" across the rest of the run rather than re-escalating on every request.

None of this is exposed as input you tune — it runs automatically on every search.

### What's the difference between a reply scraper and a conversation engagement report?

A reply scraper returns a list: every captured reply as its own record, with no summary attached. A conversation engagement report is a computed rollup over that same list — reply rate, unique repliers, like statistics — that answers "how did this post's replies perform" without you writing the aggregation code.

The distinction matters for anyone tracking conversations at scale. A list of 50 replies across 20 posts is 1,000 records to sort through by hand; a report is one number per post you can rank, threshold or alert on. Twitter X Reply Scraper — Conversation Engagement Analytics returns both from the same run — the raw list in `repliesData`, the computed rollup in `engagementReport`, and the pre-ranked highlights in `topReplies` — so you are never forced to choose between raw data and a usable summary.

### How to scrape X replies with Twitter X Reply Scraper — Conversation Engagement Analytics

1. Open the Actor on its Apify Store listing and click **Try for free**
2. Paste one or more X search URLs into `startUrls` — the same links you get by searching on x.com (required)
3. Set `maxSearchResults` and `maxReplies` to size the run, and leave `engagementAnalytics` and `topRepliesLimit` at their defaults if you want the analytics
4. Add your X session under `authToken` and `ct0` — the run will not start without them
5. Click **Start**, then watch rows land in the **Output / Dataset** tab as each post finishes, or export as JSON or CSV when the run completes

#### How to track multiple searches in one run

`startUrls` is a list — paste as many X search links as you need, one per line, to follow several conversations or keyword sets in a single run. Each URL is processed in turn, and `maxSearchResults` / `maxReplies` apply per URL, not as a shared total. A residential proxy session that succeeds on one URL is kept and reused for the next, so later URLs in the same run don't repeat the escalation ladder from scratch.

### ⬇️ Input

Every field below is read directly from `.actor/actor.json`. `startUrls` is the only required field.

| Parameter | Required | Type | Description | Example Value |
| ----- | ----- | ----- | ----- | ----- |
| `startUrls` | Yes | array | X search page links — the same URLs you get when you search on x.com. Add as many as you need to track several conversations in one run. | `["https://x.com/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"]` |
| `maxSearchResults` | No | integer | How many posts to collect per search link. Minimum `1`, maximum `5000`. Default `10`. | `25` |
| `maxReplies` | No | integer | How many direct replies to capture per post. `engagementReport` and `topReplies` are computed over exactly these captured replies. Minimum `1`, maximum `500`. Default `5`. | `25` |
| `engagementAnalytics` | No | boolean | When on, each post gets an `engagementReport` object — replies captured, reply rate, unique repliers, verified-replier share, and like/retweet/quote/view statistics. Pure local statistics, no extra requests, no AI. Default `true`. | `true` |
| `topRepliesLimit` | No | integer | Adds a `topReplies` list of each post's most-liked captured replies, ranked by like count. `0` skips highlights. Minimum `0`, maximum `500`. Default `3`. | `3` |
| `authToken` | No¹ | string (secret) | Your X account session token, or set the `AUTH_TOKEN` environment variable. Required to read replies — see note below. | *(paste from your logged-in session)* |
| `ct0` | No¹ | string (secret) | The companion CSRF value for your X session, or set the `CT0` environment variable. Required together with `authToken`. | *(paste from your logged-in session)* |
| `proxyConfiguration` | No | object | Optional Apify Proxy settings. Off by default; enable to route traffic through Apify's network from the start of the run. | `{"useApifyProxy": false}` |

¹ `authToken` and `ct0` are not marked `required` in the schema individually, but the run validates them together at startup and exits immediately with an error if both are missing — from input or from the `AUTH_TOKEN` / `CT0` environment variables.

Both `authToken` and `ct0` are declared with `isSecret: true` — Apify masks them in the Console UI and in run logs, and they are never written to the dataset.

#### Example input

```json
{
  "startUrls": [
    "https://x.com/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"
  ],
  "maxSearchResults": 25,
  "maxReplies": 20,
  "engagementAnalytics": true,
  "topRepliesLimit": 5,
  "authToken": "<your X auth_token cookie>",
  "ct0": "<your X ct0 cookie>",
  "proxyConfiguration": { "useApifyProxy": false }
}
```

**Common pitfall:** the Actor always queries X's **Latest** (chronological) search timeline — the `product` field sent to X's `SearchTimeline` endpoint is hardcoded, independent of your URL's own `f=live` or `f=top` parameter. If the URL you paste has no `q=` parameter at all (for example a profile or hashtag-explore URL instead of a search URL), the Actor silently falls back to a built-in default query rather than erroring — always confirm your link is an actual `x.com/search?q=...` URL.

### ⬆️ Output

Each dataset row is one post, with its captured replies and — when `engagementAnalytics` is on — the computed report nested inline. Output is typed JSON on every run; export as JSON, CSV or Excel from the Apify Console, or read it through the Apify API.

The default dataset view (`✨ Results — ready to download`) surfaces 12 of the fields written to each row — `tweetLink`, `tweetContent`, `tweetDate`, `handle`, `verified`, `commentCount`, `retweetCount`, `likeCount`, `viewCount`, `engagementReport`, `topReplies`, `repliesData`. Three fields that are on every row — `avatar`, `fullname` and `quoteCount` — are written but not shown in that default table; switch to the JSON or CSV export to see them.

#### Scraped results

```json
[
  {
    "tweetLink": "https://x.com/samplejournalist/status/1867564631180632295",
    "avatar": "https://pbs.twimg.com/profile_images/1690785189792735232/BmUFicth_bigger.jpg",
    "fullname": "Sample Journalist",
    "handle": "@samplejournalist",
    "verified": true,
    "tweetDate": "2026-07-20T13:38:00.000Z",
    "tweetContent": "Looking for menswear experts to comment on what to wear to a job interview. Deadline Friday. Email in bio. #journorequest",
    "commentCount": 11,
    "retweetCount": 4,
    "quoteCount": 0,
    "likeCount": 19,
    "viewCount": 8420,
    "repliesData": [
      {
        "tweetLink": "https://x.com/stylepr/status/1867567715252408465",
        "avatar": "https://pbs.twimg.com/profile_images/1730590378942681088/MbS2v-ce_bigger.jpg",
        "fullname": "Style PR",
        "handle": "@stylepr",
        "verified": true,
        "tweetDate": "2026-07-20T13:50:00.000Z",
        "tweetContent": "Happy to connect you with our menswear expert — sending over now.",
        "commentCount": 1,
        "retweetCount": 0,
        "quoteCount": 0,
        "likeCount": 8,
        "viewCount": 612
      },
      {
        "tweetLink": "https://x.com/mediamatch/status/1867585988102697438",
        "avatar": "https://pbs.twimg.com/profile_images/1148575226432802818/nLiLszhz_bigger.png",
        "fullname": "Media Matchmaker",
        "handle": "@mediamatch",
        "verified": false,
        "tweetDate": "2026-07-20T15:02:00.000Z",
        "tweetContent": "We have a stylist who covers this exact beat, DM sent.",
        "commentCount": 1,
        "retweetCount": 0,
        "quoteCount": 0,
        "likeCount": 3,
        "viewCount": null
      },
      {
        "tweetLink": "https://x.com/kikipr/status/1867602139679498305",
        "avatar": "https://pbs.twimg.com/profile_images/1854884381308895232/uPeanSn1_bigger.jpg",
        "fullname": "Kiki PR",
        "handle": "@kikipr",
        "verified": false,
        "tweetDate": "2026-07-20T16:07:00.000Z",
        "tweetContent": "Sent you an email about this.",
        "commentCount": 0,
        "retweetCount": 0,
        "quoteCount": 0,
        "likeCount": 1,
        "viewCount": 208
      }
    ],
    "engagementReport": {
      "repliesCaptured": 3,
      "reportedReplyCount": 11,
      "replyRate": 0.2727,
      "uniqueRepliers": 3,
      "repeatRepliers": 0,
      "verifiedRepliers": 1,
      "verifiedReplierShare": 0.3333,
      "sumReplyLikes": 12,
      "avgReplyLikes": 4.0,
      "medianReplyLikes": 3.0,
      "topReplyLikes": 8,
      "sumReplyRetweets": 0,
      "sumReplyQuotes": 0,
      "totalReplyViews": 820
    },
    "topReplies": [
      {
        "tweetLink": "https://x.com/stylepr/status/1867567715252408465",
        "avatar": "https://pbs.twimg.com/profile_images/1730590378942681088/MbS2v-ce_bigger.jpg",
        "fullname": "Style PR",
        "handle": "@stylepr",
        "verified": true,
        "tweetDate": "2026-07-20T13:50:00.000Z",
        "tweetContent": "Happy to connect you with our menswear expert — sending over now.",
        "commentCount": 1,
        "retweetCount": 0,
        "quoteCount": 0,
        "likeCount": 8,
        "viewCount": 612
      }
    ]
  },
  {
    "tweetLink": "https://x.com/newsdeskpr/status/1867712004829478912",
    "avatar": "https://pbs.twimg.com/profile_images/1611002233421234567/xYzAbC_bigger.jpg",
    "fullname": "Newsdesk PR",
    "handle": "@newsdeskpr",
    "verified": false,
    "tweetDate": "2026-07-21T09:12:00.000Z",
    "tweetContent": "Seeking a supply-chain economist for a piece on tariffs, quote needed by tomorrow. #journorequest",
    "commentCount": 4,
    "retweetCount": 1,
    "quoteCount": 0,
    "likeCount": 6,
    "viewCount": 2140,
    "repliesData": [],
    "engagementReport": {
      "repliesCaptured": 0,
      "reportedReplyCount": 4,
      "replyRate": 0.0,
      "uniqueRepliers": 0,
      "repeatRepliers": 0,
      "verifiedRepliers": 0,
      "verifiedReplierShare": 0.0,
      "sumReplyLikes": 0,
      "avgReplyLikes": 0.0,
      "medianReplyLikes": 0.0,
      "topReplyLikes": 0,
      "sumReplyRetweets": 0,
      "sumReplyQuotes": 0,
      "totalReplyViews": null
    },
    "topReplies": []
  },
  {
    "tweetLink": "https://x.com/econbeatwriter/status/1867730511932108801",
    "avatar": "https://pbs.twimg.com/profile_images/1502233445566778899/kLmNoP_bigger.jpg",
    "fullname": "Econ Beat Writer",
    "handle": "@econbeatwriter",
    "verified": true,
    "tweetDate": "2026-07-21T11:47:00.000Z",
    "tweetContent": "Any freelance data journalists free for a rate-card comparison piece? #journorequest",
    "commentCount": 2,
    "retweetCount": 0,
    "quoteCount": 0,
    "likeCount": 3,
    "viewCount": 990,
    "repliesData": [
      {
        "tweetLink": "https://x.com/freelancerjane/status/1867731820019283456",
        "avatar": "https://pbs.twimg.com/profile_images/1477788990011223344/qRsTuV_bigger.jpg",
        "fullname": "Freelancer Jane",
        "handle": "@freelancerjane",
        "verified": false,
        "tweetDate": "2026-07-21T11:52:00.000Z",
        "tweetContent": "I do this exact beat — reaching out now.",
        "commentCount": 0,
        "retweetCount": 0,
        "quoteCount": 0,
        "likeCount": 2,
        "viewCount": 140
      }
    ],
    "engagementReport": {
      "repliesCaptured": 1,
      "reportedReplyCount": 2,
      "replyRate": 0.5,
      "uniqueRepliers": 1,
      "repeatRepliers": 0,
      "verifiedRepliers": 0,
      "verifiedReplierShare": 0.0,
      "sumReplyLikes": 2,
      "avgReplyLikes": 2.0,
      "medianReplyLikes": 2.0,
      "topReplyLikes": 2,
      "sumReplyRetweets": 0,
      "sumReplyQuotes": 0,
      "totalReplyViews": 140
    },
    "topReplies": [
      {
        "tweetLink": "https://x.com/freelancerjane/status/1867731820019283456",
        "avatar": "https://pbs.twimg.com/profile_images/1477788990011223344/qRsTuV_bigger.jpg",
        "fullname": "Freelancer Jane",
        "handle": "@freelancerjane",
        "verified": false,
        "tweetDate": "2026-07-21T11:52:00.000Z",
        "tweetContent": "I do this exact beat — reaching out now.",
        "commentCount": 0,
        "retweetCount": 0,
        "quoteCount": 0,
        "likeCount": 2,
        "viewCount": 140
      }
    ]
  }
]
```

### How can I use the data extracted with Twitter X Reply Scraper — Conversation Engagement Analytics?

- **PR and communications teams:** run a search on request hashtags like `#journorequest` (the Actor's own default), then use `engagementReport.verifiedReplierShare` and `topReplies` to see which pitches drew credible responses before following up.
- **AI engineers and LLM developers:** an agent issues a search query, receives typed JSON with the post, its replies and the computed report already attached, and passes it straight into the model as grounded context — no aggregation step needed on the agent's side.
- **Social listening and brand analysts:** track `engagementReport.replyRate` and `verifiedRepliers` per post across a keyword set to separate posts that generated a real conversation from ones that only accumulated likes.
- **Community managers:** use `uniqueRepliers` and `repeatRepliers` to see whether a post's replies came from a broad audience or a small recurring group, and `topReplies` to find the highest-signal response worth amplifying.

### How do you monitor conversation engagement over time?

Monitoring here means re-running the same search links on a schedule and diffing the `engagementReport` fields for a given `tweetLink` between runs, rather than reading the raw reply list each time. Between runs, `repliesCaptured`, `replyRate`, `uniqueRepliers`, `verifiedReplierShare`, `avgReplyLikes` and `topReplyLikes` are the fields that move — a widening `replyRate` or a rising `verifiedReplierShare` signals a conversation gaining credible traction; a `topReplyLikes` jump signals one reply pulling ahead of the pack.

A concrete loop: schedule a run across your tracked search URLs, key each result on `tweetLink`, and compare `engagementReport` against the previous run's value for the same post. Alert when `replyRate` crosses a threshold you care about, or when a new entry appears in `topReplies` that wasn't there before.

This Actor has no built-in scheduler of its own — set up the repeated run through **Apify Schedules** in the Apify Console (or the Apify API), pointing at the same `startUrls`, and pull each run's dataset through the API to do the diff.

### Integrate Twitter X Reply Scraper — Conversation Engagement Analytics and automate your workflow

Twitter X Reply Scraper — Conversation Engagement Analytics works with any language or tool that can send an HTTP request, through the Apify API.

#### REST API with Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("<YOUR_USERNAME>/twitter-x-reply-scraper-conversation-engagement-analytics").call(run_input={
    "startUrls": ["https://x.com/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"],
    "maxSearchResults": 25,
    "maxReplies": 20,
    "engagementAnalytics": True,
    "topRepliesLimit": 5,
    "authToken": "<your X auth_token cookie>",
    "ct0": "<your X ct0 cookie>",
})

for post in client.dataset(run["defaultDatasetId"]).iterate_items():
    report = post.get("engagementReport") or {}
    print(post["handle"], post["tweetLink"], "replyRate:", report.get("replyRate"))
```

Works the same way in Go, Ruby, Node.js or plain `curl` — any client that can call the Apify API.

#### Scheduled monitoring and delivery

Set up a recurring run with **Apify Schedules** in the Console, pointed at the same `startUrls`. Each run writes to its own dataset, which you pull through the Apify API or download directly from the Console; there is no built-in webhook push from inside the Actor itself, but Apify's platform-level webhooks can be attached to a run to notify an external endpoint when it finishes.

### Is it legal to scrape X posts and replies?

Scraping publicly accessible social media content is broadly permissible in the United States: in *hiQ Labs, Inc. v. LinkedIn Corp.*, 938 F.3d 985 (9th Cir. 2019), the Ninth Circuit held that scraping data a platform makes publicly visible does not violate the Computer Fraud and Abuse Act. Twitter X Reply Scraper — Conversation Engagement Analytics returns only what your own authenticated session can already see when browsing X.

This output includes personal data — handles, display names, avatars and verified status of real, identifiable people — so GDPR and CCPA considerations apply to how you store and process it, alongside X's own Terms of Service, which govern automated access regardless of jurisdiction. Scraping for a one-off research snapshot and scraping to build a persistent, re-identifiable dataset carry different risk profiles under both frameworks.

Consult your legal team before storing this data in bulk or using it for any commercial purpose.

### ❓ Frequently asked questions

#### What happens if a search URL returns no posts?

The run logs that no posts were found for that URL and moves on to the next one in `startUrls` — no row is pushed, and nothing is charged, for a search that returns nothing.

#### Does the Actor respect the Top or Latest tab from my search URL?

No — this is the actor's one real ignored control. Whatever your pasted URL's `f=` parameter says, the Actor always requests X's **Latest** (chronological) search product; the `q=` search terms are read from your URL, but the sort tab is not.

#### How does the Actor handle X's anti-bot measures?

It escalates rather than failing on the first block. A response with HTTP 401, 403, 429 or 503, no status at all, or a GraphQL `errors` payload is treated as blocked, and the Actor retries through direct connection → Apify datacenter proxy → Apify residential proxy, retrying the residential tier up to three times with backoff. A working residential session is kept sticky and reused for the rest of the run rather than re-escalating every request. Query IDs and request-signing tokens are refreshed from X's own client bundle whenever the proxy changes.

#### Does Twitter X Reply Scraper — Conversation Engagement Analytics extract conversation engagement analytics?

Yes — turn on `engagementAnalytics` (default `true`) and every post gets an `engagementReport` object with reply rate, unique/repeat repliers, verified-replier share, and like/retweet/quote/view statistics, plus a `topReplies` array of the most-liked captured replies. Both are computed purely from replies already captured in that run — no extra request, no AI.

#### Does it capture full nested reply threads, or only direct replies?

Only direct replies to the original post. `repliesData` is filtered to entries whose `in_reply_to_status_id_str` matches the post itself — a reply-to-a-reply further down the thread is not walked or included.

#### How many posts and replies can Twitter X Reply Scraper — Conversation Engagement Analytics return per run?

`maxSearchResults` caps posts per search URL at up to 5,000 (default 10). `maxReplies` caps direct replies captured per post at up to 500 (default 5). Both are enforced in code and clamped to their documented range regardless of what value is passed.

#### How do I use this Actor to monitor conversation engagement over time?

Schedule a repeated run over the same search URLs with Apify Schedules, key each result by `tweetLink`, and diff `engagementReport` fields — `replyRate`, `uniqueRepliers`, `verifiedReplierShare`, `avgReplyLikes` — against the previous run for the same post to see what changed.

#### Does it work with Claude, ChatGPT and other AI agent frameworks?

Yes. It is callable as a standard HTTP endpoint through the Apify API, so any agent framework that can issue a request — LangChain, CrewAI, a custom tool definition — can call it and receive the post, its replies and the computed engagement report as typed JSON, ready to ground a response.

#### How does Twitter X Reply Scraper — Conversation Engagement Analytics compare to other X reply scrapers?

Checked on the Apify Store on 26 July 2026: `louisdeconinck/twitter-reply-scraper` documents the same search-URL input shape and a similar 12-field post/reply schema, and states it requires "No Authentication Required." `kaitoeasyapi/twitter-reply` is a different-shaped product built around `conversation_ids` rather than search URLs, pay-per-event at a listed $0.25/1,000 tweets, and returns a much larger nested user-profile object per reply. Neither documents a computed engagement rollup or a ranked top-replies list over the replies it captures — that summarization is what this Actor adds on top of a comparable raw schema.

#### Can I use Twitter X Reply Scraper — Conversation Engagement Analytics without managing proxies or X credentials?

Proxies: yes — leave `proxyConfiguration` at its default and the Actor handles the direct-to-datacenter-to-residential escalation and session stickiness itself. Credentials: no — you must supply your own X session (`authToken` and `ct0`, or the `AUTH_TOKEN` / `CT0` environment variables); there is no anonymous path for reading reply threads on X.

#### What happens if my X session has expired or was entered incorrectly?

The run validates `authToken` and `ct0` together before it starts collecting anything. If either is missing or empty — after checking both the input fields and the `AUTH_TOKEN` / `CT0` environment variables — the run logs an error asking you to add your session and exits immediately, without spending any charge on a partial or empty result set.

### 🔗 Related scrapers

| Scraper Name | What it extracts |
| ----- | ----- |
| Twitter Trends Scraper With Rank History & Staying Power | Trending topics with hourly rank history, peak rank and staying-power timelines |
| Twitter X Posts Scraper With Contact Links | A profile's posts with each author's resolved website, location and bio contact details |
| X Twitter Posts Search (Reposter Finder) | Posts matching a search, plus the accounts that reposted each one, ranked by follower count |

### 💬 Your feedback

Found a bug, or need a field that X exposes but this Actor doesn't return? Open an issue on the Actor's Issues tab on Apify — reports that include the search URL you used are the fastest to act on.

# Actor input Schema

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

📋 Add one or more X search page links (the same URLs you get when you search on x.com). Each link defines what topic or keywords you want to follow. Add as many as you need to track several conversations in one run. Example: https://x.com/search?q=%23journorequest+min\_replies%3A10\&src=typed\_query\&f=live

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

🎯 How many posts to collect from each search link. Higher numbers give a broader snapshot; lower numbers finish faster. Example: 10 → up to 10 posts per link. Default is 10.

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

🧵 For each post, how many replies to capture. The engagement report and top-replies ranking are computed over exactly these captured replies. Example: maxReplies=25 → the report summarises up to 25 replies per post. Default is 5.

## `engagementAnalytics` (type: `boolean`):

📊 When on, each post gets an engagementReport object summarising its captured replies: replies captured, reply-rate vs the post's reported reply count, unique repliers, verified-replier share, and sum / average / median / top reply likes. Pure local statistics — no extra requests, no AI. Default is true.

## `topRepliesLimit` (type: `integer`):

🏆 Add a topReplies list to each post containing its most-liked captured replies, ranked by like count (highest first). Example: 3 → the three most-liked replies. Set to 0 to skip highlights. Default is 3.

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

🔑 Your X account session token (or set the AUTH\_TOKEN environment variable in Apify). This lets the actor read replies using your logged-in session. Keep it private.

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

🔒 The companion security value (csrf) for your X session (or use the CT0 env var). It works together with your auth token. Keep it private.

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

🌍 Optional Apify Proxy settings for smoother cloud runs. Off by default; enable to route traffic through Apify's network.

## Actor input object example

```json
{
  "startUrls": [
    "https://x.com/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"
  ],
  "maxSearchResults": 10,
  "maxReplies": 5,
  "engagementAnalytics": true,
  "topRepliesLimit": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapier/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/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapier/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/search?q=%23journorequest+min_replies%3A10&src=typed_query&f=live"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scrapier/twitter-x-reply-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapier/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/HyPKBfgcRFkJVHFcz/builds/oaqDqAk3cWKtMBk2u/openapi.json
