# X Profile Lookup: twitter profile scraper from $2/1k (`accountable_eel/x-profile-lookup`) Actor

Twitter profile scraper and X profile API. Paste X handles or profile URLs, get one flat row each: display name, bio, location, website, join date, exact follower, following and post counts, badge type, profile picture and header. No login, no API key. Never charged for a miss.

- **URL**: https://apify.com/accountable\_eel/x-profile-lookup.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (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

from $1.00 / 1,000 profile founds

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?

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 Profile Lookup: twitter profile scraper from $2/1k

You give this actor a list of X (Twitter) handles or profile URLs and for each one it returns the
display name, bio, location, website, join date, exact follower / following / post counts, badge
type, profile picture and header image. Every number is read from the profile page's own
server-rendered data at request time. No login, no cookies, no API key, and no stored database
sits between you and the answer.

### Who it's for

Growth and partnerships teams sizing an account before they pitch it, community managers keeping a
roster of member handles current, researchers tracking how a set of accounts grows week over week,
and anyone whose CRM has a Twitter handle column that has never been enriched. If your workflow
today is "open 40 tabs and copy the follower count into a spreadsheet", this is that job as one
API call. Pricing is per profile: a few tenths of a cent for a profile that came back with data,
and nothing at all for a handle that is unused, suspended or typo'd. No seat license, no monthly
minimum, no credit system to decode.

### Why this one

- **Exact counts, not the abbreviated ones on the page.** The profile card shows "92.3M followers"
  and "74.3K posts". The page's own data carries `92385570` and `74276`, and that is what this
  actor returns. Rounded figures are useless for tracking growth; these are not.
- **No API key and no $200/month tier.** X's official API starts at $200/month for the plan most
  people need. This reads the public profile page, which anyone can open in a browser, and bills
  per profile found.
- **Never charged for a miss.** A handle nobody holds comes back as a clean `NOT_FOUND` row, a
  suspended account is labelled as suspended rather than silently returned as empty data, and
  neither one costs anything.
- **A suspended account is never sold to you as a hit.** X still renders a page for a suspended
  handle. This actor reads the status and treats it as a miss, instead of billing you for a row
  with no profile in it.
- **Handles, @handles, x.com links and old twitter.com links all work.** Paste whatever your CRM
  already stores; you do not have to normalise the column first.
- **No credential ever enters this actor.** There is no input field that could take a bearer
  token, a cookie or a password, because none is needed.

### What you get

Every row starts with the same five fields, then the profile fields.

| Field | Type | Description |
|---|---|---|
| `query` | text | The handle or URL you submitted, echoed back |
| `found` | boolean | `true` if the profile was found |
| `status` | text | `OK` on a match; `NOT_FOUND`, `BLOCKED`, `BAD_FORMAT`, or `REQUEST_FAILED` on a miss |
| `message` | text | Plain-English reason for a miss, only present when `found` is `false` |
| `scrapedAt` | ISO 8601 datetime | When the page was fetched |
| `handle` | text | The account's handle, in the casing X itself uses |
| `userId` | text | The account's stable numeric X user ID, which survives a handle change |
| `displayName` | text | The account's display name |
| `bio` | text | The bio text, as X stores it |
| `location` | text | The self-declared location, if the account sets one |
| `website` | link | The account's link, already expanded past the `t.co` wrapper |
| `joinedDate` | ISO 8601 datetime | When the account was created |
| `followersCount` | number | Exact follower count |
| `followingCount` | number | Exact following count |
| `tweetCount` | number | Exact number of posts |
| `verified` | boolean | Whether the account carries any badge |
| `verifiedType` | text | Which badge: `Blue`, `Business` or `Government` |
| `isProtected` | boolean | Whether the account is protected (posts visible to followers only) |
| `profileImageUrl` | image | Profile picture, upgraded to the 400x400 size |
| `bannerUrl` | image | Header image, if the account sets one |
| `profileUrl` | link | Canonical profile page |

Deselect any field in the Input tab's column picker to drop it from every row: `query`, `found`,
`status`, `message` and `scrapedAt` always stay.

### Price

Pay-per-event. A flat per-run fee covers session/proxy warmup; you're billed
per item only when data is actually found and returned — see
`.actor/pay_per_event.json` for exact prices. A miss is never charged.

1,000 handles through this actor: **about $2** if every one resolves, less if some do not, and
less again on a paid Apify plan. The same 1,000 profiles through X's own API means a $200/month
subscription before you read the first row.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `profiles` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~x-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"profiles":["nasa","apify","github"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

1) Open the Input tab and paste your list into "X handles or profile URLs to look up": one per
   line, as a bare handle (`nasa`), an `@handle`, or a full profile URL on either domain.
2) Leave "Try it first" on for your opening run. It caps the run at 5 profiles so you can check
   the output shape before spending on the whole list.
3) Optionally trim "Which columns do you want?" to the fields you actually need, or use "Only keep
   rows containing" and "Skip rows containing" to filter by keyword.
4) Turn "Try it first" off and press Start to run the full list.

Typical uses: enriching a CRM column of Twitter handles, sizing a list of creators before an
outreach campaign, and running the same list weekly so you can chart follower growth over time.

### Input

```json
{
  "profiles": [
    "nasa",
    "apify",
    "github"
  ]
}
```

One X (Twitter) handle or profile URL per line. Accepted formats: nasa, @nasa, https://x.com/nasa, https://twitter.com/nasa.

### Sample output

| query | found | status | handle | userId | displayName | bio | location | website | joinedDate | followersCount | followingCount | tweetCount | verified | verifiedType | isProtected | profileImageUrl | bannerUrl | profileUrl | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| nasa | true | OK | NASA | 11348282 | NASA | Making the seemingly impossible, possible. ✨ | Pale Blue Dot | http://www.nasa.gov/ | 2007-12-19T20:20:32.000Z | 92385617 | 117 | 74276 | true | Government | false | https://pbs.twimg.com/profile\_images/1321163587679784960/0ZxKlEKB\_400x400.jpg | https://pbs.twimg.com/profile\_banners/11348282/1775567134 | https://x.com/NASA | 2026-09-08T10:13:18.979Z |

A handle nobody holds, or one belonging to a suspended account, gets a row with `found: false` and
a `status` / `message` explaining which of the two it was. Neither is charged.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~x-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"profiles":["nasa","apify","github"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~x-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"profiles":["nasa","apify","github"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~x-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"profiles":["{{profile}}"]}`, mapping the row's profile into the `profiles` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Twitter Profile Scraper: X Profile API | Apify" — the agent will find and run this actor.

### Tips

- Run "Try it first" on a few accounts whose follower counts you already know, so you can confirm
  the numbers before committing a long list.
- Store `userId`, not `handle`, as your join key. Handles change; the numeric ID does not, so a
  rename shows up as a changed `handle` on the same `userId` instead of a broken row.
- Keep the parallel-request setting low on a big list. X notices bursts, and a page of `BLOCKED`
  rows costs more in wasted time than a slower, steadier run.
- `verifiedType` is more useful than `verified` on its own: `Government` and `Business` badges say
  something about the account that a paid `Blue` badge does not.
- `location` is free text the account holder typed. "Pale Blue Dot" is a real value. Do not feed
  it into a geocoder without cleaning it.
- Re-run the same list on a schedule and diff `followersCount` between runs to get growth. This
  actor keeps no history of its own.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`x-profile-lookup`) | $2 per 1,000 found profiles at the FREE tier, less on paid tiers, nothing for a miss | One flat row per handle: display name, bio, location, website, join date, exact follower / following / post counts, badge type, pictures | Profile data only. It returns nothing about the account's posts, followers list, or engagement. If you need posts, `apidojo/tweet-scraper` is the actor for that job, not this one. |
| **apidojo/tweet-scraper** | $0.40 per 1,000 tweets | Posts: the tweets themselves, with their text, media and engagement counts | A different product for a different question. Use it when you want what an account said; use this when you want who the account is. |
| **X API (official)** | From $200/month | Everything X chooses to expose, under a documented contract with rate limits you can plan against | If you are building a product on X data and need guarantees, pay for the API. This is for the enrichment case where a monthly subscription for a follower-count column makes no sense. |
| Doing it yourself | Your time, plus parsing a serialized page payload that X reformats without notice, plus telling a suspended account apart from an unused handle | The same data | The parsing, the exact-versus-rounded count handling, the suspended-versus-missing distinction, and re-testing when the page changes are the maintenance this actor absorbs. |

Prices for third-party tools are their published figures as of September 2026 and are not tracked
here; check the vendor before relying on the comparison.

### FAQ

**Why does a row say `found: false`?**
One of four reasons, and `message` always names which: nobody holds that handle (`NOT_FOUND`, a
real HTTP 404 from X), the account is suspended (`NOT_FOUND`, with "suspended" in the message),
the input is not a valid handle shape (`BAD_FORMAT`), or the request was refused or timed out
(`BLOCKED` / `REQUEST_FAILED`). None of the four is charged.

**Am I charged for a miss?**
No. The charge fires only when a profile actually resolves and its data is returned. Suspended
accounts, unused handles and blocked requests all produce a row and cost nothing.

**Do I need an X API key, a login, or a cookie?**
No. There is no input field that accepts one. The actor reads the public profile page, the same
page a logged-out visitor sees.

**Are the follower counts exact or rounded?**
Exact. The visible profile card rounds to "92.3M"; the page's underlying data carries the real
integer, and that is the number in `followersCount`. The same is true of `followingCount` and
`tweetCount`.

**Is this a live check or a stored database?**
Live. Every run fetches the current page. There is no snapshot behind it, which is also why
growth tracking needs a schedule plus your own storage rather than a history field here.

**What happens to protected accounts?**
They still return a row. X serves the profile header for a protected account, so display name,
bio, join date and the three counts are all present; `isProtected: true` flags it. What a
protected account does not expose is its posts, which this actor does not return for any account.

**What does `verifiedType` mean?**
It is X's own label for the badge on the account: `Blue` for a paid subscription, `Business` for
an organisation, `Government` for an official body. `null` means the page carried no badge for
that account.

**Does an old twitter.com URL still work?**
Yes. `twitter.com`, `mobile.twitter.com` and `x.com` links all resolve to the same handle before
the request is made.

**Is this GDPR-relevant?**
What comes back is an account's own public, self-published profile information, the same fields
any visitor sees without logging in. It returns no private data, no email addresses and no
follower lists.

**Can an AI agent call this directly?**
Yes. It is on the Apify MCP server, so an agent in Claude, Cursor or another MCP client can find
and run it by name, or you can call the REST endpoint shown above from any script or workflow.

### Related actors

- [Threads Profile Lookup](https://apify.com/accountable_eel/threads-profile-lookup): the same
  profile-stats shape for Meta's Threads, which is where a lot of X audiences went.
- [TikTok Profile Lookup](https://apify.com/accountable_eel/tiktok-profile-lookup): follower,
  like and video counts for a list of TikTok handles.
- [LinkedIn Company Lookup](https://apify.com/accountable_eel/linkedin-company-lookup): once a
  handle is qualified, pull the company behind it.
- [Google News Lookup](https://apify.com/accountable_eel/google-news-lookup): check what coverage
  an account or brand is getting.

# Actor input Schema

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

One X (Twitter) handle or profile URL per line. Accepted formats: nasa, @nasa, https://x.com/nasa, https://twitter.com/nasa. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

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

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "profiles": [
    "nasa",
    "apify",
    "github"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "columns": [
    "handle",
    "userId",
    "displayName",
    "bio",
    "location",
    "website",
    "joinedDate",
    "followersCount",
    "followingCount",
    "tweetCount",
    "verified",
    "verifiedType",
    "isProtected",
    "profileImageUrl",
    "bannerUrl",
    "profileUrl"
  ],
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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 = {
    "profiles": [
        "nasa",
        "apify",
        "github"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/x-profile-lookup").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",
        "apify",
        "github",
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/x-profile-lookup").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",
    "apify",
    "github"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/x-profile-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/x-profile-lookup"
        }
    }
}

```

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/y4vwjGbQXrY96VGpX/builds/Eqe56qiVfMy4KmgAh/openapi.json
