# X (Twitter) Lead & Contact Extractor (`guezi/x-lead-contact-extractor`) Actor

Turn a list of X (Twitter) accounts into a B2B lead list: business emails, including ones hidden as name (at) domain (dot) com, the real website behind the profile link, Instagram, LinkedIn, YouTube, TikTok and Telegram handles, location and audience tier. One row per account. No login needed.

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

## Pricing

from $1.00 / 1,000 account profileds

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) Lead & Contact Extractor

Turn a list of X (Twitter) accounts into a working B2B lead list. Give it the
accounts you care about and it returns one row per account with the business
email address published on the profile — including the ones written to defeat
harvesters, like `booking (at) agency (dot) com` — plus the real website behind
the profile link, every other link the account publishes, the Instagram,
LinkedIn, YouTube, TikTok and Telegram accounts named in the bio, location,
audience size band and account age. No X account, no login, no browser
extension.

### What you can do with it

- **Build an outreach list from a niche.** Feed in the accounts in your category
  and keep only the ones that publish an email address you can actually write to.
- **Enrich a CRM.** You already have handles on your records; this fills in the
  email, the company site, the phone number and the other places that account
  lives online.
- **Qualify influencers and creators before you pitch.** Audience band, account
  age, verification and a real booking address in one row — no more hunting
  through bios by hand.
- **Find reachable small businesses.** Cap the audience size so household names
  drop out and independent operators stay in.
- **Filter a long list down to a target segment.** Keep only accounts whose bio
  mentions "founder", "agency" or "recruiting", drop anything that says "parody",
  and keep only the ones based in the cities you sell to.
- **De-duplicate a messy list.** Handles, @handles and profile links all work,
  and the same account supplied twice comes back once.

### What you get

One row per account, always with the same columns. Here is a real row, shortened
to the fields that matter most — the full list is in **Output fields** below:

```json
{
  "inputProfile": "timthetatman",
  "username": "timthetatman",
  "displayName": "timthetatman👑",
  "profileUrl": "https://x.com/timthetatman",
  "bio": "Entertainer, streamer, nerd. Business Inquiries: timthetatman@wmeagency.com",
  "emails": ["timthetatman@wmeagency.com"],
  "primaryEmail": "timthetatman@wmeagency.com",
  "phones": [],
  "website": "https://youtube.com/timthetatman",
  "allLinks": ["https://youtube.com/timthetatman"],
  "socialProfiles": {
    "instagram": null,
    "linkedin": null,
    "youtube": { "handle": "timthetatman", "url": "https://www.youtube.com/timthetatman" },
    "tiktok": null,
    "telegram": null
  },
  "location": null,
  "followers": 3113580,
  "following": 1346,
  "postCount": 61831,
  "followerTier": "mega",
  "accountAgeYears": 13.7,
  "joinedAt": "2012-12-07T22:26:26.000Z",
  "isVerified": false,
  "hasSubscriptionBadge": true,
  "isProtected": false,
  "avatarUrl": "https://pbs.twimg.com/profile_images/1490099066776215553/wCdfis1s.jpg",
  "hasContactDetails": true,
  "unavailable": false,
  "scrapedAt": "2026-08-05T22:13:30.092Z"
}
```

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `profiles` | array of text | — | **Required.** The accounts to turn into leads. An account name, an `@name` or a link to the profile all work. |
| `requireEmail` | boolean | `false` | Keep only accounts where an email address was found. |
| `requireWebsite` | boolean | `false` | Keep only accounts that publish a website. |
| `minFollowers` | integer | `0` | Skip accounts smaller than this. `0` means no minimum. |
| `maxFollowers` | integer | `0` | Skip accounts larger than this. `0` means no maximum. |
| `bioKeywords` | array of text | all | Keep only accounts whose bio mentions at least one of these words. Not case sensitive. |
| `excludeKeywords` | array of text | none | Drop accounts whose bio mentions any of these words. Not case sensitive. |
| `locations` | array of text | all | Keep only accounts whose location line mentions one of these places. Not case sensitive. |
| `accountsAtOnce` | integer | `3` | How many accounts to work through at the same time. 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 |
| --- | --- |
| `username`, `displayName`, `profileUrl`, `userId` | Who the row is about. |
| `inputProfile` | Exactly what you supplied, so the row joins back to your list. |
| `emails` | Every email address published on the profile, lower-cased, in the order written. Hidden forms are read out in full. |
| `primaryEmail` | The first address found — the one to use when you only want one. |
| `phones` | Telephone numbers published on the profile. Empty on almost every account — see the limits below. |
| `website` | The real destination behind the profile link, never the shortened form. |
| `allLinks` | Every link the account publishes, profile link and bio links together. |
| `bioLinks` | Just the links written inside the bio text. |
| `socialProfiles` | The Instagram, LinkedIn, YouTube, TikTok and Telegram accounts named on the profile, each as a handle and a ready-made link. |
| `bio`, `location`, `joinedAt` | The profile text as published. |
| `followers`, `following`, `postCount`, `mediaPostCount`, `likesGiven` | Audience and activity. |
| `followerTier` | `nano` (under 10k), `micro` (10k–100k), `mid` (100k–500k), `macro` (500k–1M), `mega` (1M+). |
| `accountAgeYears` | How long the account has existed, to one decimal place. |
| `hasContactDetails` | `true` when an email address or a website was found. |
| `isVerified`, `verifiedType`, `hasSubscriptionBadge`, `isProtected`, `possiblySensitive` | Badges, privacy and content status. |
| `avatarUrl`, `bannerUrl` | Full-resolution profile images. |
| `pinnedPostId`, `professionalCategory`, `birthdate` | Extra context on the accounts that publish it, empty on the rest. |
| `unavailable`, `unavailableReason` | On every row: `false` for an account that was read, `true` plus the reason for one that no longer exists or cannot be viewed. |
| `scrapedAt` | When the row was collected. |

Every row carries every column, including the rows for accounts that could not be
read, so a CSV or Excel export lands in your CRM with a stable set of columns.

### Pricing

This actor is **pay per result**, in two parts:

- **$1.00 per 1,000 accounts** you get a row for.
- **$4.00 per 1,000 of those rows** where a real way to reach the account — an
  email address or a website — was actually found.

Nothing else is billed: no time-based charge, and no charge for accounts your
filters removed before they reached the dataset.

Worked example. A list of 1,000 accounts, of which 700 turn out to publish an
email address or a website, costs `$1.00 + $2.80 = $3.80`. Turn on
**Only accounts with an email address** and the accounts with nothing to find are
dropped before they are billed at all, so you pay for leads rather than for
lookups.

### Limits & what this actor cannot do

- This actor reports what an account publishes on its own profile. It never
  guesses, generates or "verifies" an address that is not written there — a
  fabricated address is worse than no address.
- Some accounts hide their address in ways that are deliberately not decoded,
  because doing so would invent addresses that do not exist. "Engineer at
  acme.com" is a job, not a mailbox, and it is treated as one.
- Telephone numbers are almost never published on X profiles. The `phones`
  column exists for the rare account that does publish one, but across the
  accounts checked while building this actor it was empty every time. Treat it
  as a bonus, never as a reason to buy — email and website are what this actor
  reliably delivers.
- Keyword search across all of X is not part of what X publishes openly, so this
  actor works from the list of accounts you supply rather than from a search
  term.
- The lists of who follows an account are not publicly available. Follower and
  following counts are included.
- Protected (private) accounts still publish their bio, links, location and
  counts, so they still produce a usable lead row.
- Accounts that were renamed, suspended or deleted are reported with
  `unavailable: true` and the reason X gives, rather than silently dropped —
  unless you asked for accounts with contact details only, in which case they are
  left out entirely.
- Follower counts, bios and links are a snapshot at the moment of collection and
  keep changing afterwards.
- Where X publishes no value for a field it is left empty rather than filled with
  a zero that would be wrong.
- Speed depends on the size of the job and on X's own response times.
- X's terms govern automated access, and the contact details this actor collects
  are personal data. You are responsible for using them lawfully, in line with
  the source site's terms and with applicable privacy and marketing law
  (including GDPR and CAN-SPAM where they apply to you).

### FAQ

**Do I need an X account?**
No. Nothing is needed 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.

**Do you guess or generate email addresses?**
Never. Every address in a row was written on that profile by its owner. Addresses
disguised as `name (at) domain (dot) com`, `name[at]domain.com` or
`name -at- domain -dot- com` are read back out in full, but nothing is invented.

**Why do some accounts come back with no email?**
Because they publish none. Most accounts on X publish a website but no address;
that is why `hasContactDetails` and the **Only accounts with an email address**
switch exist — so you can see, and pay for, exactly the rows that are useful.

**Can I schedule it?**
Yes. Schedule it on Apify to run daily or weekly over a watchlist and pick up
accounts as they add or change their contact details.

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

# Actor input Schema

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

The accounts to turn into leads. Paste an account name, an @name, or a link to the profile — all three work. One row comes back per account.

## `requireEmail` (type: `boolean`):

Keep only accounts where an email address was found on the profile.

## `requireWebsite` (type: `boolean`):

Keep only accounts that publish a website.

## `minFollowers` (type: `integer`):

Skip accounts smaller than this. Leave at 0 for no minimum.

## `maxFollowers` (type: `integer`):

Skip accounts larger than this — useful for finding reachable small businesses and creators rather than household names. Leave at 0 for no maximum.

## `bioKeywords` (type: `array`):

Keep only accounts whose bio mentions at least one of these words or phrases. Not case sensitive. Leave empty to keep every account.

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

Drop accounts whose bio mentions any of these words or phrases. Not case sensitive.

## `locations` (type: `array`):

Keep only accounts whose location line mentions one of these places. Not case sensitive. Accounts that publish no location are skipped when this is set.

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

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

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

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

## Actor input object example

```json
{
  "profiles": [
    "CouRageJD"
  ],
  "requireEmail": false,
  "requireWebsite": false,
  "minFollowers": 0,
  "maxFollowers": 0,
  "bioKeywords": [
    "founder",
    "agency",
    "coach"
  ],
  "excludeKeywords": [
    "parody",
    "fan account"
  ],
  "locations": [
    "London",
    "New York"
  ],
  "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": [
        "CouRageJD",
        "@SypherPK",
        "https://x.com/NICKMERCS"
    ],
    "bioKeywords": [
        "founder",
        "agency",
        "coach"
    ],
    "excludeKeywords": [
        "parody",
        "fan account"
    ],
    "locations": [
        "London",
        "New York"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("guezi/x-lead-contact-extractor").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": [
        "CouRageJD",
        "@SypherPK",
        "https://x.com/NICKMERCS",
    ],
    "bioKeywords": [
        "founder",
        "agency",
        "coach",
    ],
    "excludeKeywords": [
        "parody",
        "fan account",
    ],
    "locations": [
        "London",
        "New York",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("guezi/x-lead-contact-extractor").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": [
    "CouRageJD",
    "@SypherPK",
    "https://x.com/NICKMERCS"
  ],
  "bioKeywords": [
    "founder",
    "agency",
    "coach"
  ],
  "excludeKeywords": [
    "parody",
    "fan account"
  ],
  "locations": [
    "London",
    "New York"
  ]
}' |
apify call guezi/x-lead-contact-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,guezi/x-lead-contact-extractor"
        }
    }
}

```

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/rOawtGpaJIVIxmbs6/builds/08VYGfsk1J66sHD1B/openapi.json
