# X (Twitter) Tweet Details Scraper — URL to Full Record (`foxlabs/x-tweet-details-scraper`) Actor

Paste tweet addresses or ids; get each tweet's current likes, replies, reposts and quotes, its text, media, links and flags, and its author's full account record (followers, bio, website, verification). Reads tweets of any age. No login, no API key.

- **URL**: https://apify.com/foxlabs/x-tweet-details-scraper.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 tweets

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## X (Twitter) Tweet Details Scraper — URL to Full Record 𝕏

**Paste tweet addresses or ids; get each tweet's current numbers, its text, media and links — and the full account record of whoever posted it.** Reads tweets of any age, including ones from 2006. No login, no API key, no browser: charged per row.

- 🔢 **All four engagement counts** — likes, replies, reposts, quotes — plus the engagement total, **engagement per 1,000 followers of the author** and engagement per hour. Every row says in `countsFrom` where its counts came from, because X states only two of the four on a single tweet
- 👤 **The author's whole record on every row** — followers, following, posts, bio, location, website, verification and its type, join date. One read per author, not per tweet, and those reads are not charged to you
- 🕰️ **Any age.** The tweet endpoint answered id `20` (21 March 2006) — history an account timeline cannot reach
- 🖼️ **Media, links and context** — photos and videos with direct URLs and sizes, expanded links, hashtags, mentions, language, the quoted tweet, the tweet it replies to, edit and sensitive flags
- 🤖 **Agent-ready** — MCP, and pay-per-event so an AI agent can run it and settle the bill itself

### What does this Actor do?

It reads X's own **embed (syndication) backend** — the service that renders embedded tweets on other people's websites. For each line you give it asks that service for the tweet, and then, once per author, reads the author's newest embedded page. That second read is what turns a thin answer into a full record: X states a tweet's like count and reply count but **not** its repost or quote count, and only eight fields about its author (measured 2026-10-19). The author's page states all four counts and the 38-field account.

**Use it when you need:** the current numbers on tweets you already know; UGC or campaign measurement from a list of links; the account behind a viral post; a check of whether tweets are still up.

**Use something else when:** you need **keyword or hashtag search**, **reply texts** or **view counts** — X's embed backend serves none of those, so this Actor does not claim them. To watch accounts rather than single tweets, use **[X (Twitter) Account Monitor](https://apify.com/foxlabs/x-account-monitor)**; for account records, **[X (Twitter) Profile Scraper](https://apify.com/foxlabs/x-profile-scraper)**.

### Quick start (API)

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~x-tweet-details-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "tweets": ["https://x.com/jack/status/20", "https://x.com/NASA/status/2111180963661435219"], "enrichAuthor": true }'
```

Prefer no code? Open the **Input** tab, paste your tweet links one per line, and click **Start**.

### Getting started (3 steps)

1. **Paste the links.** `https://x.com/NASA/status/2111180963661435219`, `twitter.com/...`, a `/photo/1` suffix or a bare id (`20`) — all work, mixed in one list. Duplicate lines are read once and charged once.
2. **Leave author enrichment on.** It is what gives you the repost and quote counts and the author's record, and it costs one read per author, not per tweet.
3. **Turn on `includeAuthorRows`** if you also want one row per author, the way the X Profile Scraper delivers them.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `tweets` | array | — | Tweet addresses or numeric ids, one per line. **Required.** |
| `enrichAuthor` | boolean | `true` | Reads each author's newest page once: adds the repost and quote counts for tweets on that page and the author's full record to every row. Not charged. |
| `includeAuthorRows` | boolean | `false` | One extra row per author whose page was read. Charged as an author row. |
| `postedWithinDays` | integer | `0` | Only tweets from the last N days (0 = no limit). |
| `minEngagement` | integer | `0` | Only tweets with at least this much engagement. |
| `maxTweets` | integer | `0` | Cost cap for the whole run (0 = every line). |
| `concurrency` | integer | `8` | Tweets read side by side. |
| `proxyConfiguration` | object | Apify Proxy (datacenter) | Everything is asked for by numeric id, which X answers from datacenter addresses — no residential proxy needed. |

### Output

One row per tweet (plus one per author with `includeAuthorRows`). Download as JSON, CSV, Excel or read it from the API.

```json
{
  "type": "tweet",
  "input": "https://x.com/jack/status/20",
  "id": "20",
  "url": "https://x.com/jack/status/20",
  "text": "just setting up my twttr",
  "createdAt": "2006-03-21T20:50:14.000Z",
  "language": "en",
  "likes": 309218,
  "replies": 18099,
  "reposts": 124610,
  "quotes": 7322,
  "engagement": 459249,
  "engagementPer1kFollowers": 36.638,
  "countsFrom": "author-page",
  "countsComplete": true,
  "conversationCount": 18099,
  "media": [],
  "mediaTypes": [],
  "hasVideo": false,
  "hashtags": [],
  "mentions": [],
  "links": [],
  "isEdited": false,
  "authorId": "12",
  "authorHandle": "jack",
  "authorName": "jack",
  "authorFollowers": 12534906,
  "authorFollowing": 4620,
  "authorPosts": 29365,
  "authorBio": "no state is the best state",
  "authorVerified": true,
  "authorCreatedAt": "2006-03-21T20:50:14.000Z",
  "authorEnriched": true,
  "scrapedAt": "2026-10-19T12:26:11.512Z"
}
```

#### Tweet fields

| Field | Type | Description |
|---|---|---|
| `input` | string | The line of your input this row came from |
| `id` / `url` | string | Tweet id and its address on x.com |
| `text` | string | The tweet's text as X states it |
| `createdAt` / `ageHours` | — | ISO 8601 UTC, and hours since it went up |
| `likes` / `replies` | integer | Always X's own numbers (the tweet endpoint states both) |
| `reposts` / `quotes` | integer | X's numbers when the tweet is on its author's newest page; **`null`** otherwise — never 0 |
| **`countsFrom`** | string | `author-page` (all four counts are X's) or `tweet-endpoint` (likes and replies only) |
| **`countsComplete`** | boolean | Whether all four counts are filled |
| `engagement` | integer | The counts that are stated, added up |
| `engagementPer1kFollowers` | number | `engagement / author followers × 1,000` — comparable across accounts of different sizes |
| `engagementPerHour` | number | How fast it is travelling |
| `media[]` / `mediaCount` / `mediaTypes` / `hasVideo` | — | Photos and videos: type, direct URL, width, height |
| `hashtags[]` / `mentions[]` / `links[]` | array | As X states them (links are the expanded addresses) |
| `language` / `sensitive` / `isEdited` / `isStaleEdit` / `editCount` | — | Language code and the flags X states; `null` when it states none |
| `conversationId` / `inReplyToTweetId` / `inReplyToHandle` | string | The thread, and the tweet this one answers |
| `quotedTweetId` / `quotedTweetText` | string | The tweet it quotes, when X embeds one |
| `authorId` / `authorHandle` / `authorName` / `authorFollowers` / `authorFollowing` / `authorPosts` / `authorListedIn` / `authorBio` / `authorLocation` / `authorWebsite` / `authorVerified` / `authorVerifiedType` / `authorCreatedAt` / `authorProfileImage` | — | The author's record, flattened onto the row so a tweet list is a table an analyst can sort. Filled when `enrichAuthor` is on (`authorEnriched` says so) |
| `scrapedAt` | string | When this row was read |

A line that cannot be delivered is written as a free `type: "status"` row with the reason — not a tweet address or id, a tweet X has no public record of (deleted, protected or suspended), an id X does not recognise, or a tweet the run could not read. **Status rows are never charged.**

### Use cases

- **Campaign and UGC measurement.** A list of links from a brief or a report becomes a table with current numbers and the author behind each post.
- **Influencer proof.** `engagementPer1kFollowers` next to `authorFollowers` shows whether a post actually travelled for the size of its account.
- **Crisis and reputation watch.** Re-run the same list and see how the numbers moved; a deleted tweet comes back as a free status row saying so.
- **Research and journalism.** Any tweet still public, however old, with its exact timestamp, media and links.
- **Feeding a dashboard or an LLM.** Flat rows, stable field names, ISO timestamps, `null` where X said nothing.

### Integration

**MCP / AI agents:** ask for this Actor by name — `foxlabs/x-tweet-details-scraper` — on the Apify MCP server. It is pay-per-event, so an agent can run it and settle the bill per row.

**JavaScript**

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('foxlabs/x-tweet-details-scraper').call({
    tweets: ['https://x.com/NASA/status/2111180963661435219', 'https://x.com/jack/status/20'],
    enrichAuthor: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((row) => `${row.authorHandle}: ${row.engagement} (${row.countsFrom})`));
```

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("foxlabs/x-tweet-details-scraper").call(run_input={
    "tweets": ["https://x.com/jack/status/20"],
    "enrichAuthor": True,
})
for row in client.dataset(run.default_dataset_id).iterate_items():
    if row["type"] == "tweet":
        print(row["authorHandle"], row["likes"], row["reposts"], row["countsFrom"])
```

Written for `apify-client` 3 for Python, where `call()` returns a run object; with version 1 or 2 read `run["defaultDatasetId"]`.

**Automation (n8n / Zapier / Make):** webhook or schedule → HTTP request to the Actor API with your link list → handle the JSON dataset → push to a sheet, CRM or dashboard.

### Pricing

Pay-per-event: **$0.30 per 1,000 delivered tweets** (~$0.0003 each) and **$1 per 1,000 author rows** (~$0.001 each, only with `includeAuthorRows`). Status rows and tweets left out by a filter are **free** — you pay for rows you receive. The author pages the enrichment reads are **not charged**: they are our cost, not your row.

| What you ask for | Rows | Approx. cost |
|---|---|---|
| 300 campaign tweets with author records | 300 tweets | ~$0.09 |
| 10,000 tweets, one pass | 10,000 tweets | ~$3.00 |
| 1,000 tweets re-checked daily for a week | 7,000 tweets | ~$2.10 |

You also pay Apify for the compute the run uses, which is small: one read per tweet plus one per author, no browser. Apify's **free tier** includes $5/month of credits.

### Limits — what this Actor cannot do

These are not omissions; they are what X's embed backend serves (measured 2026-10-19):

- **No repost or quote count for an old tweet.** X states neither on a single tweet; they come from the author's newest page, so a tweet older than that page has them `null`. `countsFrom` and `countsComplete` say so on every row instead of filling in zeros. Likes and replies are always X's own.
- **No view counts.** X states none on an embedded tweet.
- **No reply texts.** The reply *count* yes; X's conversation endpoint answers 404.
- **No search.** No keyword, hashtag or list timelines — this Actor works from links you already have.
- **Deleted, protected and suspended tweets** are not readable and come back as free status rows.
- **The rate budget is shared.** X's embed backend limits how fast one account may read, across all of its runs — two runs of this family started at the same time will refuse each other's reads (measured 2026-10-19). Run them one after another, and leave the speed setting at its default; a read X refuses is tried again at the end of the run, one at a time.

### FAQ

**Do I need an X account, login or API key?** No. This reads X's public embed service.

**Why are `reposts` and `quotes` sometimes null?** Because X does not state them on a single tweet. They come from the author's newest embedded page; if the tweet is older than that page, nobody can read them this way, and an invented 0 would be worse than an honest `null`.

**What does author enrichment cost me?** Nothing per tweet: one read per author per run, and those reads are not charged. 500 tweets from 20 accounts means 520 reads and 500 charged rows.

**How old a tweet can it read?** Any public one. Measured: id `20` from March 2006.

**Can I give the same tweet twice?** Yes — duplicate lines are read once and charged once.

**How fresh are the numbers?** Every run reads live; `scrapedAt` says when. Re-running the same list is the intended way to track movement.

### Troubleshooting

| What you see | What it means |
|---|---|
| `X has no public tweet with id …` | Deleted, protected, suspended or never public. Free row. |
| `X does not recognise "…" as a tweet id` | The number is not a tweet id (X answers 400). Check the address you pasted. Free row. |
| `Not a tweet: give its address …` | The line is neither a tweet address nor a numeric id. Free row. |
| `… tweets were not on their author's newest page` | Those rows have likes and replies from X and `null` reposts and quotes. Expected for older tweets. |
| `… author pages could not be read` | The enrichment read failed for that author; its tweets still carry likes and replies. |
| Run ends FAILED with `did not answer the opening check` | X did not answer us at all. Nothing was read or charged; try again. |

### Is it legal to scrape this data?

This Actor reads a public embed service with no login and collects what X itself publishes on public tweets and profiles. It stores no private data and no credentials. Always check your own use against X's Terms of Service and the data-protection rules that apply to you, especially if you keep personal data or use it for monitoring individuals.

### Support & contact

Found a defect or need a field that is missing? Open an issue on the Actor's **Issues** tab with the run id — every run keeps a `SOURCE_REPORT` record with what it saw, read, delivered and left out, which makes a report quick to answer.

### Changelog

#### 0.1 — 2026-10-19

- First release. Tweet addresses or bare ids, de-duplicated; likes, replies, reposts and quotes with `countsFrom` saying which the source stated; author enrichment once per author with the 38-field account record on every row; media, links, hashtags, mentions, quoted and replied-to tweets, edit flags; optional author rows; filters for age and engagement; free status rows for deleted, unrecognised and malformed lines.

***

Part of the **[foXLabs data platform](https://data.foxlabs.com.tr/)** — public-data company, contact, social, procurement and AI-search intelligence scrapers.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/x-tweet-details-scraper/changelog.md

# Actor input Schema

## `tweets` (type: `array`):

One per line: a tweet address (https://x.com/NASA/status/2111180963661435219, twitter.com/... and the /photo/1 suffix work too) or its numeric id. Duplicate lines are read once and charged once.

## `enrichAuthor` (type: `boolean`):

X states only the like count and the reply count on a single tweet, and only eight fields about its author. With this on, each author's newest page is read once per run, which adds the repost and quote counts for every tweet on that page and the author's full account record (followers, following, posts, bio, location, website, verification, account age) to every row. It costs one read per author, not per tweet, and those reads are not charged to you.

## `includeAuthorRows` (type: `boolean`):

One extra row for each author whose page was read: the account itself, with the same fields as the X Profile Scraper's rows. Charged as an author row.

## `postedWithinDays` (type: `integer`):

0 means no limit. A tweet older than this is read but not delivered and not charged.

## `minEngagement` (type: `integer`):

Likes + reposts + replies + quotes, as far as the source states them. A tweet under this is not delivered and not charged.

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

A cost cap. 0 means every line in the list is read.

## `concurrency` (type: `integer`):

How many tweets are read side by side. Measured 2026-10-18: 25 reads in 12 seconds with no refusal, so the default is comfortable.

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

Left as it is, the Actor uses Apify Proxy datacenter addresses — everything it reads is asked for by numeric id, and X answers those from datacenter addresses, so no residential proxy is needed.

## Actor input object example

```json
{
  "tweets": [
    "https://x.com/NASA/status/2111180963661435219",
    "https://x.com/jack/status/20",
    "https://x.com/nike/status/2109382322516799677"
  ],
  "enrichAuthor": true,
  "includeAuthorRows": false,
  "postedWithinDays": 0,
  "minEngagement": 0,
  "maxTweets": 0,
  "concurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "tweets": [
        "https://x.com/NASA/status/2111180963661435219",
        "https://x.com/jack/status/20",
        "https://x.com/nike/status/2109382322516799677"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/x-tweet-details-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 = {
    "tweets": [
        "https://x.com/NASA/status/2111180963661435219",
        "https://x.com/jack/status/20",
        "https://x.com/nike/status/2109382322516799677",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/x-tweet-details-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 '{
  "tweets": [
    "https://x.com/NASA/status/2111180963661435219",
    "https://x.com/jack/status/20",
    "https://x.com/nike/status/2109382322516799677"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call foxlabs/x-tweet-details-scraper --silent --output-dataset

```

## MCP server setup

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