# X (Twitter) Post Details Scraper (`guezi/x-post-details-scraper`) Actor

Look up any X (Twitter) post by link or ID and get one row per post: full text, likes, reposts, replies, quotes, bookmarks, views, author profile, images and video files, hashtags, mentions and links. Deleted and private posts are marked, not dropped. No X account or login needed.

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

## Pricing

$0.80 / 1,000 post scrapeds

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/platform/actors/running/actors-in-store#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

## X (Twitter) Post Details Scraper

Give it a list of X (Twitter) post links — or just the numeric post IDs — and get
back one clean row per post: the full text, the publication time, every
engagement number X publishes including views, the author's profile, and
downloadable image and video files. Posts that have been deleted or locked down
come back clearly marked rather than silently missing, so the list you put in is
the list you get out. No X account, no login, no browser extension.

### What you can do with it

- **Turn a list of links into a table.** You already have post links in a
  spreadsheet, a campaign report or a client brief — this fills in the text,
  the numbers, the author and the media behind every one of them.
- **Measure influencer and sponsored posts.** Paste the links a creator sent you
  and check the likes, reposts, replies, quotes, bookmarks and views yourself.
- **Watch a post age.** Schedule the same list daily and record how engagement
  moves in the hours and days after publication.
- **Archive posts that matter.** Capture the exact wording, timing and media of a
  post before it is edited or taken down, with the author's follower count at
  the moment of collection.
- **Fact-check and research.** Resolve a post ID out of a screenshot or a
  citation into a complete, dated record — the exact wording, the author, the
  time it went up and the engagement it drew.
- **Collect media at source.** Pull the original-resolution images and the best
  available video files attached to each post.

### What you get

One row per post. A real, abridged row from a live run:

```json
{
  "requestedPostId": "2085101081260904717",
  "postId": "2085101081260904717",
  "url": "https://x.com/NASA/status/2085101081260904717",
  "text": "In the early hours of Wednesday, Aug. 5, a portion of a @SpaceX rocket impacted on the Moon's surface. In this video, we answer some of your questions.",
  "isLongForm": false,
  "createdAt": "2026-08-05T20:30:11.000Z",
  "language": "en",
  "conversationId": "2085101081260904717",
  "likes": 650,
  "reposts": 139,
  "replies": 100,
  "quotes": 18,
  "bookmarks": 70,
  "views": 205192,
  "isRepost": false,
  "isReply": false,
  "isQuote": false,
  "author": {
    "userId": "11348282",
    "username": "NASA",
    "displayName": "NASA",
    "bio": "Making the seemingly impossible, possible. ✨",
    "location": "Pale Blue Dot",
    "joinedAt": "2007-12-19T20:20:32.000Z",
    "followers": 92245567,
    "following": 119,
    "postCount": 74298,
    "verifiedType": "Government",
    "hasSubscriptionBadge": true,
    "avatarUrl": "https://pbs.twimg.com/profile_images/1321163587679784960/0ZxKlEKB.jpg"
  },
  "media": [
    {
      "type": "video",
      "imageUrl": "https://pbs.twimg.com/media/HO_EYeIWMAAl-yw.png?format=png&name=orig",
      "width": 720,
      "height": 405,
      "videoUrl": "https://video.twimg.com/amplify_video/2085101033903071233/vid/avc1/1280x720/NbDpoBdvreV6p1qW.mp4?tag=14",
      "videoBitrate": 2176000,
      "videoDurationSeconds": 133.5,
      "videoAspectRatio": "16:9"
    }
  ],
  "hashtags": [],
  "cashtags": [],
  "mentions": [{ "username": "SpaceX", "userId": "34743251", "displayName": "SpaceX" }],
  "links": [],
  "possiblySensitive": false,
  "postedVia": "Sprinklr",
  "wasEdited": false,
  "communityNote": null,
  "unavailable": false,
  "detailLevel": "full",
  "inputPost": "https://x.com/NASA/status/2085101081260904717",
  "scrapedAt": "2026-08-05T21:49:09.679Z"
}
```

A post that cannot be viewed still gets a row:

```json
{
  "requestedPostId": "1962431326201712747",
  "postId": null,
  "url": null,
  "unavailable": true,
  "unavailableReason": "The account limits who can see its posts",
  "detailLevel": "none",
  "inputPost": "https://x.com/i/web/status/1962431326201712747",
  "scrapedAt": "2026-08-05T21:49:09.213Z"
}
```

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `posts` | array of text | — | **Required.** The posts to look up. A link to the post, an old twitter.com link, a mobile link, a link with `/photo/1` or tracking parameters on the end, or the numeric post ID on its own — all of them work. |
| `includeQuotedPost` | boolean | `true` | When a post quotes another one, attach the quoted post's text, author and engagement to the row. |
| `includeUnavailablePosts` | boolean | `true` | Keep a marked row for posts that cannot be viewed, so your input list keeps its shape. Turn it off to leave them out entirely. |
| `postsAtOnce` | integer | `3` | How many posts to work through in parallel. Raise it for long lists. |
| `countryCode` | select | `us` | Which country to browse from. Affects what X shows publicly in a few regions. |

### Output fields

| Field | Description |
| --- | --- |
| `requestedPostId` | The post ID you asked for, always present, so your join keys survive. |
| `postId`, `url` | Identifier and permanent link of the post that came back. |
| `text` | The post's own text, in full — long posts are returned complete, not cut off. On a repost this is the short `RT @account:` line X gives the repost itself; the reposted post's full text is under `repostOf`. |
| `createdAt` | Publication time, ISO-8601 UTC. |
| `language` | The language X detected for the post. |
| `conversationId` | The ID of the conversation the post belongs to. |
| `likes`, `reposts`, `replies`, `quotes`, `bookmarks` | Engagement at the moment of collection. On a repost these are the repost's own counts, not the original's — the original's are under `repostOf`. |
| `views` | How many times X reports the post was seen. |
| `isRepost`, `isReply`, `isQuote`, `isLongForm` | Post type flags. |
| `replyToPostId`, `replyToUsername` | What the post replies to, when it is a reply. |
| `repostOf`, `quotedPost` | The original post, when this one repeats or quotes another — with its own text, author and engagement. |
| `author` | The account that wrote the post: name, bio, location, join date, follower and post counts, badges, avatar. |
| `media` | Images, video files and GIFs, at the best quality available, with dimensions and duration. |
| `hashtags`, `cashtags`, `mentions`, `links` | Entities found in the post. Links are the real destinations, not shortened. |
| `postedVia` | The app the post was published from. |
| `wasEdited`, `possiblySensitive`, `communityNote` | Extra context. Each is filled in only where X publishes it for that post, and left empty otherwise. |
| `detailLevel` | `full` when X returned the complete record, `partial` when X returned a reduced one and some engagement fields are therefore empty, `none` for an unavailable post. |
| `unavailable`, `unavailableReason` | Set when a post cannot be viewed, in plain English. |
| `inputPost`, `scrapedAt` | The exact value you supplied, and when the row was collected. |

### Pricing

This actor is **pay per result**. You are charged for each row that lands in your
dataset and for nothing else — no time-based charge. Rows for posts that turn out
to be deleted or private count as results because looking them up is the work; if
you would rather not pay for them, set **Keep a row for posts that cannot be
viewed** to off and they are left out.

A list of 1,000 posts produces about 1,000 rows and costs about $0.80. A daily
re-check of 50 posts costs under $0.05 a day.

### Limits & what this actor cannot do

- Replies written by other people underneath a post are not publicly available.
  The reply *count* is included; the replies themselves are not.
- Keyword search across all of X is not part of what X publishes openly, so this
  actor works from the posts you supply rather than a search term.
- Protected (private) accounts are not publicly viewable. Their posts are
  reported as unavailable rather than returned empty.
- Deleted, suspended and withheld posts are reported as unavailable rather than
  silently skipped, with the reason stated in plain English.
- Occasionally X returns a reduced record for a post. Those rows are marked
  `partial` and the fields X did not publish are left empty rather than filled
  with a zero that would be wrong.
- Likes, reposts, replies, quotes, bookmarks and views are a snapshot at the
  moment of collection and keep changing afterwards.
- X publishes view counts for most recent posts but not for every older one.
  Where X publishes none, `views` is left empty rather than set to zero.
- Look up a repost and the row describes the repost itself: X gives a repost its
  own short `RT @account:` line and its own counts, which are not the original's.
  The post that was reposted is attached in full under `repostOf`, with its
  complete text, its author and its real engagement numbers.
- Asked for a repost, X sometimes answers with the original post instead. Both
  IDs are kept on the row, so you can always tell which one you received.
- Speed depends on the size of the list and on X's own response times.
- X's terms govern automated access. You are responsible for using the data
  lawfully, in line with the source site's terms, and for handling any personal
  data in line with applicable privacy law.

### FAQ

**Do I need an X account?**
No. Nothing is needed from you beyond the list of posts you want.

**Does it need my login, password or a paid X subscription?**
No. None of those are used at any point.

**What exactly can I paste into the post list?**
Anything that identifies a post: `https://x.com/NASA/status/2085101081260904717`,
the same link on twitter.com or mobile.twitter.com, a link with `/photo/1` or a
`?s=20` tracking suffix, or the bare number `2085101081260904717`. Duplicates are
collapsed automatically.

**Can I schedule it?**
Yes. Schedule it on Apify to re-check the same list hourly, daily or weekly and
watch engagement move over time.

**Is the data complete?**
For a public post, yes — text in full, all engagement numbers X publishes, the
author, the media and the entities. What X does not publish openly, such as the
replies underneath a post, is not available from anywhere and is not invented
here.

**Why is `views` empty on some older posts?**
Because X does not publish a view count for them. The field is left empty rather
than filled with a zero that would be wrong.

**How do I get the results into my tools?**
Export from Apify as CSV, Excel, JSON, XML or an HTML table, push into Google
Sheets, or pull them straight into your own system.

# Actor input Schema

## `posts` (type: `array`):

The posts to look up. Paste a link to the post, an old twitter.com link, or the numeric post ID on its own — all of them work. Links with /photo/1 or tracking parameters on the end are fine.

## `includeQuotedPost` (type: `boolean`):

When a post quotes another one, attach the quoted post's own text, author and engagement to the row.

## `includeUnavailablePosts` (type: `boolean`):

Deleted posts, posts from private or suspended accounts and IDs that do not exist get a row marked unavailable, so your input list keeps its shape. Turn this off to leave them out entirely.

## `postsAtOnce` (type: `integer`):

How many posts to work through in parallel. Raise it for long lists.

## `countryCode` (type: `string`):

The country to browse from. Affects which posts X shows publicly in a few regions.

## Actor input object example

```json
{
  "posts": [
    "https://x.com/NASA/status/2085101081260904717"
  ],
  "includeQuotedPost": true,
  "includeUnavailablePosts": true,
  "postsAtOnce": 3,
  "countryCode": "us"
}
```

# 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 = {
    "posts": [
        "https://x.com/NASA/status/2085101081260904717",
        "https://twitter.com/elonmusk/status/1812256998588662068",
        "2084745539187273851"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("guezi/x-post-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 = { "posts": [
        "https://x.com/NASA/status/2085101081260904717",
        "https://twitter.com/elonmusk/status/1812256998588662068",
        "2084745539187273851",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("guezi/x-post-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 '{
  "posts": [
    "https://x.com/NASA/status/2085101081260904717",
    "https://twitter.com/elonmusk/status/1812256998588662068",
    "2084745539187273851"
  ]
}' |
apify call guezi/x-post-details-scraper --silent --output-dataset

```

## MCP server setup

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