# X (Twitter) Profile Scraper (`guezi/x-profile-scraper`) Actor

Turn a list of X (Twitter) handles into a clean table of profiles: followers, following, bio, website, location, join date, badges, post counts, avatar and banner. Optionally attach each account's latest public post and pinned post. No X account or login needed. Export to CSV, JSON or Sheets.

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

## Pricing

$0.90 / 1,000 profile 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) Profile Scraper

Turn a list of X (Twitter) account names into a clean table of profile records —
follower and following counts, bio, website, location, join date, badge status,
post counts, avatar and banner images, and the pinned post identifier. One row
per account, thousands of accounts per run. No X account, no login, and nothing
to install.

### What you can do with it

- **Qualify influencers and creators in bulk.** Drop in a list of handles and
  get follower counts, post volume and badge status for all of them at once.
- **Enrich a CRM or lead list.** Match your contacts to their X accounts and
  pull the bio, website and location straight onto the record.
- **Track how accounts grow.** Run the same list on a schedule and keep a
  history of follower counts over time.
- **Map a competitive set.** Compare audience size, posting volume and account
  age across every brand in your category in one table.
- **Clean a list you inherited.** Accounts that were renamed, suspended or never
  existed come back marked, so you can prune the list instead of guessing.
- **Grab profile imagery.** Avatar and banner images come through at full size,
  ready to use in a report or a deck.

### What you get

One row per account (abridged real output, with the optional post attachments
switched on):

```json
{
  "inputProfile": "nasa",
  "userId": "11348282",
  "username": "NASA",
  "profileUrl": "https://x.com/NASA",
  "displayName": "NASA",
  "bio": "Making the seemingly impossible, possible. ✨",
  "website": "http://www.nasa.gov/",
  "bioLinks": [],
  "location": "Pale Blue Dot",
  "joinedAt": "2007-12-19T20:20:32.000Z",
  "followers": 92245538,
  "following": 119,
  "postCount": 74298,
  "mediaPostCount": 28063,
  "likesGiven": 16904,
  "isVerified": false,
  "verifiedType": "Government",
  "hasSubscriptionBadge": true,
  "isProtected": false,
  "avatarUrl": "https://pbs.twimg.com/profile_images/1321163587679784960/0ZxKlEKB.jpg",
  "bannerUrl": "https://pbs.twimg.com/profile_banners/11348282/1775567134",
  "pinnedPostId": "2084734580737634576",
  "unavailable": false,
  "unavailableReason": null,
  "scrapedAt": "2026-08-05T21:45:07.653Z",
  "latestPost": {
    "postId": "2085116299223425392",
    "url": "https://x.com/NASA/status/2085116299223425392",
    "text": "On Aug. 12, you have the chance to experience a total solar eclipse from anywhere in the world…",
    "createdAt": "2026-08-05T21:30:39.000Z",
    "likes": 240,
    "reposts": 60,
    "replies": 36,
    "views": 69961,
    "isPinned": false,
    "media": [{ "type": "video", "videoUrl": "https://video.twimg.com/amplify_video/2085116218021736448/vid/avc1/1280x720/Eh8miiwmo8s_CHWk.mp4" }]
  },
  "pinnedPost": {
    "postId": "2084734580737634576",
    "url": "https://x.com/NASA/status/2084734580737634576",
    "text": "Lunar landers, assemble. In clean rooms across the country, commercial lunar landers are preparing for upcoming missions…",
    "createdAt": "2026-08-04T20:13:51.000Z",
    "likes": 3600,
    "reposts": 522,
    "replies": 179,
    "views": 1335499,
    "isPinned": true
  }
}
```

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `profiles` | array of text | — | **Required.** The accounts to collect. An account name, an `@name` or a link to the profile all work, and duplicates are removed for you. |
| `includeLatestPost` | boolean | `false` | Attach the most recent post shown on the account's public profile, with its text, engagement and media. |
| `includePinnedPost` | boolean | `false` | Attach the post the account pinned to the top of its profile, when it has one. |
| `accountsAtOnce` | integer | `3` | How many accounts to work through in parallel. Raise it to finish long lists faster. |
| `countryCode` | select | `us` | Which country to browse from. |

### Output fields

| Field | Description |
| --- | --- |
| `inputProfile` | Exactly what you supplied, so results join back onto your own list. |
| `userId`, `username`, `profileUrl` | Permanent account identifier, current handle and link. |
| `displayName`, `bio` | Display name and profile description. |
| `website` | The account's link, resolved to its real destination rather than the shortened form. |
| `bioLinks` | Any other links written inside the bio, also resolved. |
| `location` | The free-text location on the profile. |
| `joinedAt` | When the account was created, ISO-8601 UTC. |
| `followers`, `following` | Audience size and how many accounts it follows. |
| `postCount`, `mediaPostCount` | Total posts, and how many of them carry photos or video. |
| `likesGiven` | How many posts this account has liked. |
| `isVerified`, `verifiedType` | Legacy verification flag, and what X classifies the account as (`Business`, `Government`, …). |
| `hasSubscriptionBadge` | Whether the account shows the current checkmark. |
| `isProtected` | Whether the account is private. |
| `avatarUrl`, `bannerUrl` | Profile picture and header image, at full size. |
| `pinnedPostId` | Identifier of the pinned post, when the account has one. |
| `latestPost` | The most recent post shown on the account's public profile, when you asked for it. When that post is a repost it is flagged, and the original is included alongside. |
| `pinnedPost` | The pinned post, when you asked for it. |
| `unavailable`, `unavailableReason` | Set when an account cannot be read — suspended, withheld or never existed — so your list keeps its shape. |
| `scrapedAt` | When the row was collected. |

### Pricing

This actor is **pay per result**: you are charged for each account row that
lands in your dataset, and for nothing else. There is no time-based charge, and
attaching the latest or pinned post costs nothing extra.

A list of 1,000 accounts costs about $0.90. Accounts you supply twice are collapsed before anything is
collected, so a messy list never bills twice for the same account.

### Limits & what this actor cannot do

- Keyword search across all of X is not part of what X publishes openly, so this
  actor works from the accounts you supply rather than a search term.
- The *lists* of who follows an account, or who it follows, are not publicly
  available. Follower and following **counts** are included on every row.
- Private (protected) accounts still publish their profile, so you get the full
  row — but their posts are not public, so no latest or pinned post is attached.
- Suspended, withheld and non-existent accounts are reported with `unavailable`
  set and a plain-English reason rather than being silently dropped, so those
  entries still join back onto your list.
- Entries that are not a usable X account name, and repeats of an account you
  listed more than once, are skipped rather than collected, so they produce no
  row. Each one is named in the run log.
- The attached latest post is the most recent post X shows on that account's
  public profile. For a handful of very large accounts X shows a mixed selection
  of older posts there instead of the newest ones, so for those accounts the
  attached post can be older than the account's true latest post.
- Follower counts, post counts and engagement figures 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.
- `isVerified` is the old verification flag and is `false` for almost every
  account today. The checkmark you see on X now lives in `hasSubscriptionBadge`,
  and `verifiedType` tells you whether X classifies the account as a business,
  a government body and so on.
- An account name can be changed by its owner. `userId` never changes, so it is
  the safer key to store if you re-run the same list over time.
- Speed depends on the size of the job and on X's own response times; no fixed
  throughput is promised.
- 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 required from you beyond the list of accounts you want.

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

**How many accounts can I put in one run?**
Thousands. The list is de-duplicated first, a failure on one account never stops
the rest, and you can raise `accountsAtOnce` to finish long lists faster.

**Can I schedule it?**
Yes. Schedule it on Apify to run daily or weekly over the same list and build a
history of how those accounts grow.

**What happens to accounts that were deleted or renamed?**
You still get a row, marked `unavailable` with the reason, so you can clean your
list instead of losing track of which entry failed.

**Is the data complete?**
It is everything X publishes on a public profile. Anything X keeps private —
follower lists, private accounts' posts, email addresses — is not available to
anyone reading a public profile, and this actor does not pretend otherwise.

**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 programmatically into your own system.

# Actor input Schema

## `profiles` (type: `array`):

The accounts to collect. Paste an account name, an @name, or a link to the profile — all three work, and duplicates are removed for you. Thousands of accounts in one run are fine.

## `includeLatestPost` (type: `boolean`):

Add the most recent post shown on each account's public profile, with its text, engagement and media. Slower, because each account needs a second look.

## `includePinnedPost` (type: `boolean`):

Add the post the account pinned to the top of its profile, if it has one. Slower, because each account needs a second look.

## `accountsAtOnce` (type: `integer`):

How many accounts to work through in parallel. Raise it to finish long lists faster.

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

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

## Actor input object example

```json
{
  "profiles": [
    "nasa"
  ],
  "includeLatestPost": false,
  "includePinnedPost": false,
  "accountsAtOnce": 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 = {
    "profiles": [
        "nasa",
        "@elonmusk",
        "https://x.com/OpenAI"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("guezi/x-profile-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 = { "profiles": [
        "nasa",
        "@elonmusk",
        "https://x.com/OpenAI",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("guezi/x-profile-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 '{
  "profiles": [
    "nasa",
    "@elonmusk",
    "https://x.com/OpenAI"
  ]
}' |
apify call guezi/x-profile-scraper --silent --output-dataset

```

## MCP server setup

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