# X (Twitter) Profile Scraper: Followers, Bio & Tweets (`scrapers_lat/x-profile-scraper`) Actor

X (Twitter) profile scraper and API. Look up any public account by username or URL and export the full profile plus recent tweets: follower count, following, bio, location, verification, business category and tweet engagement. JSON, CSV, Excel. No login, no API key.

- **URL**: https://apify.com/scrapers_lat/x-profile-scraper.md
- **Developed by:** [Scrapers Lat](https://apify.com/scrapers_lat) (community)
- **Categories:** Social media, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 4 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.15 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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) Profile & Tweets Scraper](https://scrapers.lat/banners/x-profile-scraper.png)](https://console.apify.com/actors/QrAg8o7kbGND1JnZM/input)

## X (Twitter) Profile Scraper: Followers, Bio & Tweets

The most complete **X (Twitter) profile scraper** and **social media data API** on Apify. Give it a username or profile URL and get back every public profile field X exposes, from **follower count** and verification to business category and join date, plus the account's most recent tweets with full engagement metrics. No login, no cookies, no API key, no official Twitter API access token required.

Ideal for **influencer data**, lead generation, competitor research, audience analysis, and social media monitoring at scale. Export clean **JSON, CSV, or Excel** and drop it straight into your CRM, spreadsheet, or data pipeline.

Here is one real result, with every field the actor returns (the `recentTweets` array is trimmed to 1 tweet to keep the sample short; every value shown is real):

```json
{
  "profileImageUrl": "https://pbs.twimg.com/profile_images/1321163587679784960/0ZxKlEKB.jpg",
  "profileImageShape": "Square",
  "username": "NASA",
  "displayName": "NASA",
  "url": "https://x.com/NASA",
  "userId": "11348282",
  "bio": "Making the seemingly impossible, possible. ✨",
  "bioUrls": [],
  "bioHashtags": [],
  "bioMentions": [],
  "website": "http://www.nasa.gov/",
  "location": "Pale Blue Dot",
  "isVerified": true,
  "isBlueVerified": true,
  "verified": false,
  "verifiedType": "Government",
  "isIdentityVerified": false,
  "verifiedReason": "This account is verified because it is a government or multilateral organization account.",
  "verifiedSince": "2009-08-07T19:53:50.941Z",
  "isProtected": false,
  "canDm": false,
  "professionalType": null,
  "professionalCategories": [],
  "isBusinessAccount": true,
  "affiliatesCount": 87,
  "affiliation": null,
  "accountCreated": "Wed Dec 19 20:20:32 +0000 2007",
  "accountCreatedISO": "2007-12-19T20:20:32.000Z",
  "birthdate": { "day": 1, "month": 10, "year": 1958 },
  "followerCount": 92383412,
  "fastFollowerCount": 0,
  "normalFollowerCount": 92383412,
  "followingCount": 117,
  "tweetCount": 74272,
  "mediaCount": 28136,
  "listedCount": 96943,
  "favouritesCount": 16984,
  "bannerUrl": "https://pbs.twimg.com/profile_banners/11348282/1775567134",
  "defaultProfileImage": false,
  "withheldInCountries": [],
  "pinnedTweetId": null,
  "pinnedTweetIds": [],
  "numTweetsFetched": 1,
  "recentTweets": [
    {
      "id": "2095621168208248911",
      "url": "https://x.com/NASA/status/2095621168208248911",
      "text": "RT @Astro_Jessica: Photo or it didn't happen. Some initial views from my camera during Tuesday's spacewalk...",
      "createdAt": "Thu Sep 03 21:13:15 +0000 2026",
      "createdAtISO": "2026-09-03T21:13:15.000Z",
      "likeCount": 0,
      "replyCount": 0,
      "retweetCount": 240,
      "quoteCount": 0,
      "bookmarkCount": 0,
      "viewCount": 46693,
      "lang": "en",
      "conversationId": "2095621168208248911",
      "possiblySensitive": null,
      "isReply": false,
      "inReplyToUsername": null,
      "isQuote": false,
      "quotedStatusId": null,
      "isRetweet": true,
      "source": "Twitter for iPhone",
      "hashtags": [],
      "mentions": ["Astro_Jessica"],
      "links": [],
      "media": []
    }
  ],
  "observedAt": "2026-09-04T05:37:12.054Z",
  "error": null
}
```

**📥 [Input](https://apify.com/scrapers_lat/x-profile-scraper/input-schema) · 📤 [Output](https://apify.com/scrapers_lat/x-profile-scraper/output-schema) · 💰 [Pricing](https://apify.com/scrapers_lat/x-profile-scraper/pricing) · ▶️ [Examples](https://apify.com/scrapers_lat/x-profile-scraper/examples)**

![Apify](https://img.shields.io/badge/Platform-Apify-1CE1CE?logo=apify\&logoColor=white)
![Coverage](https://img.shields.io/badge/Coverage-Worldwide-blue)
![Output](https://img.shields.io/badge/Output-JSON%20%7C%20CSV%20%7C%20Excel-orange)
![Billing](https://img.shields.io/badge/Billing-Pay%20per%20result-brightgreen)

### Table of contents

- [What it does](#what-it-does)
- [Why this X profile scraper](#why-this-x-profile-scraper)
- [Use cases](#use-cases)
- [Quickstart](#quickstart)
- [Input reference](#input-reference)
- [Output reference](#output-reference)
- [How it compares](#how-it-compares)
- [Run via API and CLI](#run-via-api-and-cli)
- [Fetch results](#fetch-results)
- [Billing and limits](#billing-and-limits)
- [FAQ and troubleshooting](#faq-and-troubleshooting)

### What it does

Give the actor a list of X (Twitter) handles or profile URLs. For each one it reads the public profile and writes a single normalized record containing the full profile plus the account's most recent tweets. Handles can be provided as `@name`, `name`, or a full profile URL such as `https://x.com/nasa`; duplicates are removed automatically. Missing source values are returned as `null`, never invented, and each tweet carries its own engagement metrics (likes, replies, retweets, quotes, bookmarks, views), entities (hashtags, mentions, links), and media.

The actor reads only public data. Protected, suspended, or non-existent accounts return a record with a populated `error` field instead of profile data, and are never charged.

### Why this X profile scraper

- **No API key and no login.** No Twitter developer account, no OAuth token, no cookies to paste.
- **Every public field, honest nulls.** Follower and following counts, verification type and reason, business/creator category, affiliate badge, birthdate (when public), banner, pinned tweets and more. If X does not expose a value, you get `null`, never a fake.
- **Follower counts you can trust.** `followerCount` plus the `fastFollowerCount` / `normalFollowerCount` split X reports internally.
- **ISO timestamps.** Every date is returned both in the raw X format and as sortable ISO 8601 (`accountCreatedISO`, `createdAtISO`).
- **Profile and timeline in one record.** No second run to fetch recent tweets.
- **Bulk and fast.** Pass hundreds of handles in one run; profiles are fetched concurrently.

### Use cases

- **Influencer marketing:** rank creators by follower count, engagement, verification, and category.
- **Lead generation:** pull bio links, websites, locations, and open-DM status for outreach.
- **Competitor and brand monitoring:** track follower growth and posting activity over time.
- **Audience and market research:** enrich a list of usernames into a full social media dataset.
- **Data pipelines:** feed clean JSON/CSV into dashboards, CRMs, or notebooks.

### Quickstart

Open the actor, paste this into the input, and press Run. It returns the NASA profile and its 20 most recent tweets.

```json
{
  "handles": ["NASA"],
  "maxTweetsPerUser": 20
}
```

Add more handles to the `handles` array to scrape several accounts in one run. Set `maxTweetsPerUser` to `0` if you only want profile fields and no tweets.

### Input reference

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `handles` | string\[] | yes | (none) | Public X handles or profile URLs to scrape, one record per handle. Accepts `@name`, `name`, or `https://x.com/name`. Duplicates are removed. |
| `startUrls` | string\[] | no | `[]` | Optional list of full X profile URLs. Merged with `handles` and de-duplicated. |
| `maxTweetsPerUser` | integer | no | `20` | Maximum recent tweets to keep per handle, `0` to `40`. X's public timeline returns up to about 40 recent tweets in one page. Set `0` for profile only. |
| `maxProfiles` | integer | no | (none) | Optional cap on how many profiles to process in one run. Leave empty for no limit. |

### Output reference

One dataset item per handle. Types: `string`, `number`, `boolean`, `object`, `object[]`, or `null` when the source value is absent.

| Field | Type | Description |
|---|---|---|
| `profileImageUrl` | string | Full-resolution profile avatar URL. |
| `profileImageShape` | string | Avatar shape, for example `Circle`, `Square`, or `Hexagon`. |
| `username` | string | The account handle (screen name). |
| `displayName` | string | Display name shown on the profile. |
| `url` | string | Canonical profile URL on x.com. |
| `userId` | string | Numeric X user ID as a string. |
| `bio` | string | Profile description / bio. |
| `bioUrls` | string\[] | Expanded links contained in the bio text. |
| `bioHashtags` | string\[] | Hashtags contained in the bio text. |
| `bioMentions` | string\[] | @mentions contained in the bio text. |
| `website` | string | Website link from the profile, or `null`. |
| `location` | string | Self-reported location, or `null`. |
| `isVerified` | boolean | Whether the account carries any verified badge. |
| `isBlueVerified` | boolean | Whether the account has an X (blue) verified badge. |
| `verified` | boolean | Legacy verification flag. |
| `verifiedType` | string | Verification type, for example `Government`, `Business`, or `null`. |
| `isIdentityVerified` | boolean | Whether the account passed X identity verification. |
| `verifiedReason` | string | Human-readable reason the account is verified, or `null`. |
| `verifiedSince` | string | ISO 8601 timestamp of when verification was granted, or `null`. |
| `isProtected` | boolean | Whether the account is protected (private). |
| `canDm` | boolean | Whether the account accepts direct messages from anyone. |
| `professionalType` | string | `Business`, `Creator`, or `null` for a professional account. |
| `professionalCategories` | string\[] | Business category names for a professional account. |
| `isBusinessAccount` | boolean | Whether the account is a business/organization account. |
| `affiliatesCount` | number | Number of affiliated accounts, or `null`. |
| `affiliation` | object | Parent-organization badge (`label`, `url`, `badgeUrl`), or `null`. |
| `accountCreated` | string | Account creation timestamp in raw X format. |
| `accountCreatedISO` | string | Account creation timestamp as ISO 8601. |
| `birthdate` | object | Public birthday parts (`day`, `month`, `year`), or `null`. Only publicly shared parts are filled. |
| `followerCount` | number | Number of followers. |
| `fastFollowerCount` | number | X internal "fast follower" count. |
| `normalFollowerCount` | number | X internal "normal follower" count. |
| `followingCount` | number | Number of accounts followed. |
| `tweetCount` | number | Total tweets posted. |
| `mediaCount` | number | Total media items posted. |
| `listedCount` | number | Number of lists the account appears on. |
| `favouritesCount` | number | Number of likes the account has given. |
| `bannerUrl` | string | Profile banner image URL, or `null`. |
| `defaultProfileImage` | boolean | Whether the account still uses the default avatar. |
| `withheldInCountries` | string\[] | Country codes where the account is withheld. |
| `pinnedTweetId` | string | ID of the pinned tweet, or `null`. |
| `pinnedTweetIds` | string\[] | All pinned tweet IDs. |
| `numTweetsFetched` | number | Count of tweets returned in `recentTweets`. |
| `recentTweets` | object\[] | Most recent tweets. Each holds `id`, `url`, `text`, `createdAt`, `createdAtISO`, `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `bookmarkCount`, `viewCount`, `lang`, `conversationId`, `possiblySensitive`, `isReply`, `inReplyToUsername`, `isQuote`, `quotedStatusId`, `isRetweet`, `source`, `hashtags`, `mentions`, `links`, and `media`. |
| `observedAt` | string | ISO 8601 timestamp of when the record was collected. |
| `error` | string | `null` on success. On a failed handle a single item with a populated `error` field is written instead and is not charged. |

### How it compares

Every value below reflects what each actor advertises for public X (Twitter) profile scraping. This actor is a strict superset: it accepts the same inputs and returns the same fields, plus more.

| Capability | This actor | apidojo/twitter-user-scraper | data-slayer/twitter-user | lead.gen.labs/twitter-x-profile-details |
|---|---|:--:|:--:|:--:|
| Username / handle input | Yes | Yes | Yes | Yes |
| Profile URL input | Yes | Yes | No | Partial |
| No login / no API key | Yes | Yes | Yes | Yes |
| Follower / following counts | Yes | Yes | Yes | Yes |
| Fast vs normal follower split | Yes | No | No | No |
| Verification type | Yes | Yes | Partial | Partial |
| Verified reason and date | Yes | No | No | No |
| Business / creator category | Yes | Yes | No | No |
| Affiliate badge + affiliate count | Yes | Partial | Partial | No |
| Public birthday | Yes | No | No | No |
| Bio links / hashtags / mentions | Yes | No | No | No |
| Recent tweets with engagement | Yes | Partial | No | No |
| ISO 8601 timestamps | Yes | No | No | No |
| Open-DM flag | Yes | Yes | No | No |

Named competitors are independent third-party actors; capabilities are summarized from their public Apify listings and may change.

### Run via API and CLI

Start a run and wait for it to finish, then read the dataset. Replace `<TOKEN>` with your Apify API token.

Run synchronously and get dataset items in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapers_lat~x-profile-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"handles":["NASA","OpenAI"],"maxTweetsPerUser":20}'
```

Start a run asynchronously:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapers_lat~x-profile-scraper/runs?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"handles":["nasa"],"maxTweetsPerUser":10}'
```

Apify CLI:

```bash
apify call scrapers_lat/x-profile-scraper \
  --input '{"handles":["NASA"]}'
```

### Fetch results

Every run writes to a dataset. Fetch items as JSON, CSV, or Excel by changing `format`:

```bash
## JSON
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"

## CSV
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"

## Paginate large datasets
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&offset=1000&limit=1000"
```

`<DATASET_ID>` is returned as `defaultDatasetId` in the run object. Use `offset` and `limit` to page through large result sets. `clean=true` drops empty and internal fields.

### Billing and limits

- **Pay per result.** You are charged per profile record returned (`result` event). See the [pricing tab](https://apify.com/scrapers_lat/x-profile-scraper/pricing) for the current per-result price.
- **No charge on failure.** A handle that cannot be read (not found, suspended, protected, rate-limited) is written with a populated `error` field and is not charged.
- **Spend cap respected.** Set `maxTotalChargeUsd` on the run; once reached, the actor stops emitting and charging further billable results.
- **Free Apify plans** are capped at 10 handles per run. Upgrade for more.

### FAQ and troubleshooting

**Do I need a Twitter/X API key or developer account?**
No. The actor needs only public data, so there is no API key, OAuth token, or login. Just enter handles and run.

**Why did a handle return an `error`?**
The account is protected, suspended, does not exist, or X rate-limited the request at that moment. Error records are not charged. Re-run rate-limited handles.

**How many tweets do I get per handle?**
X's public timeline exposes up to about 40 recent tweets in one page, so `maxTweetsPerUser` above 40 is capped by what X returns. Set it to `0` for profile fields only.

**Can I pass profile URLs instead of handles?**
Yes. `@nasa`, `nasa`, and `https://x.com/nasa` are all accepted and normalized to the same handle. You can also put URLs in `startUrls`.

**Can I look up accounts by numeric user ID?**
Not at this time. X does not expose the by-ID lookup to unauthenticated requests reliably, so this actor works from handles and profile URLs, which are stable and public.

**Why is `viewCount` or `bookmarkCount` null on some tweets?**
X does not expose those counters for every tweet in the public timeline. Missing values are returned as `null`, never invented.

**Is this an official X tool?**
No. This actor is independent and has no affiliation with X (formerly Twitter). It reads only data that is publicly available.

### Related scrapers

- [X (Twitter) Search & Tweets Scraper](https://apify.com/scrapers_lat/x-twitter-scraper): search X for tweets by keyword, hashtag, or query.
- [Farcaster Casts Scraper](https://apify.com/scrapers_lat/farcaster-casts-scraper): posts and profiles from the Farcaster network.
- [Behance Scraper](https://apify.com/scrapers_lat/behance-scraper): creative portfolios and project data.
- [DeviantArt Scraper](https://apify.com/scrapers_lat/deviantart-scraper): art deviations and creator profiles.
- [Dribbble Scraper](https://apify.com/scrapers_lat/dribbble-scraper): design shots and designer profiles.

### More scrapers at scrapers.lat

Built and maintained by [scrapers.lat](https://scrapers.lat), where we publish scrapers for US and Latin American public platforms: company registries, government data, finance, e-commerce and more. Browse the catalog or request a custom scraper at [scrapers.lat](https://scrapers.lat).

***

> Independent tool, not affiliated with X (formerly Twitter). Accesses only publicly available profile data.
>
> </content>

</invoke>

# Actor input Schema

## `handles` (type: `array`):

Public X (Twitter) handles to scrape, one record per handle. Accepts @name, name, or a profile URL such as https://x.com/nasa. Add one or more.

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

Optional list of full X profile URLs, for example https://x.com/nasa. Merged with Handles and de-duplicated. Handy when you already have profile links.

## `maxTweetsPerUser` (type: `integer`):

Maximum number of recent tweets to keep per handle (0 to 40). X's public timeline returns up to about 40 recent tweets in one page, so higher values are capped by what X exposes. Set 0 for profile fields only (faster).

## `maxProfiles` (type: `integer`):

Optional cap on how many profiles to process in one run, after de-duplication. Leave empty for no limit. Free Apify plans are always capped at 10 per run.

## Actor input object example

```json
{
  "handles": [
    "nasa",
    "OpenAI"
  ],
  "startUrls": [],
  "maxTweetsPerUser": 20
}
```

# Actor output Schema

## `results` (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 = {
    "handles": [
        "nasa",
        "OpenAI"
    ],
    "startUrls": [],
    "maxTweetsPerUser": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapers_lat/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 = {
    "handles": [
        "nasa",
        "OpenAI",
    ],
    "startUrls": [],
    "maxTweetsPerUser": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapers_lat/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 '{
  "handles": [
    "nasa",
    "OpenAI"
  ],
  "startUrls": [],
  "maxTweetsPerUser": 20
}' |
apify call scrapers_lat/x-profile-scraper --silent --output-dataset

```

## MCP server setup

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