# Tiktok Contact Extractor (`coregent/tiktok-contact-extractor`) Actor

TikTok Contact Extractor finds public contact details from TikTok creators, including emails, phones, websites, Instagram, YouTube, and profile data. Use it for influencer outreach, lead generation, creator partnerships, and contact-ready CRM exports.

- **URL**: https://apify.com/coregent/tiktok-contact-extractor.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Categories:** Lead generation, Social media, Automation
- **Stats:** 41 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 creator profile processeds

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/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

## TikTok Contact Extractor

Extract public emails, phones, websites, and outreach clues from TikTok creators with source attribution, confidence scoring, and contact-ready lead exports.

[![TikTok Contact Extractor](https://raw.githubusercontent.com/coregentdevspace/tiktok-contact-extractor-assets/main/thumbnail-tiktok-contact-extractor.jpg)](https://apify.com/coregent/tiktok-contact-extractor)

### What does this actor do?

**TikTok Contact Extractor** is an Apify Actor that extracts publicly available contact details from TikTok creators, including:

- **Emails** — extracted from bios and linked websites
- **Phone numbers** — extracted from bios and linked websites when public
- **Instagram, YouTube, X and Threads handles** — from bios or linked pages, with clickable URLs
- **External URLs** — bio links, websites, contact pages
- **Profile metadata** — followers, verification status, display name, bio text
- **Source attribution** — where each contact clue was found (bio, website, contact page)
- **Confidence scores** — how trustworthy each contact value is
- **Lead quality indicators** — outreach readiness and lead scoring

This actor is designed as a **contact discovery and lead-enrichment tool**, not a general TikTok scraper. It helps answer:

- Which TikTok creators have public contact information?
- Which creators are contactable right now?
- Which contact details came from bio text versus linked websites?
- Which creator leads look most trustworthy and complete?

### Why use this actor?

- **Multi-source contact extraction** — bio text, link-in-bio websites, and contact pages
- **Clear source attribution** — every contact field shows where it was found
- **Confidence scoring** — heuristic scores per email and phone based on source quality
- **Lead qualification** — contact readiness levels and lead quality scores
- **CRM-ready flat output** — one row per creator, easy to import anywhere
- **Multiple input modes** — direct profiles, keyword discovery, and hashtag discovery
- **Deduplication** — creators found from multiple sources are merged automatically
- **Every row is contactable** — creators with no email are
  filtered out, and where we can tell in advance they are skipped before you are charged
- **Cost controls** — per-source caps and a skip list that stops you paying twice for creators you already have
- **Structured error handling** — partial results with error details, not broken runs

### Who this is for

| You are | You want |
|---|---|
| **Influencer / creator marketing** | A shortlist of creators you can actually email today, not a follower dump |
| **Agency or brand partnerships** | Contact details plus enough signal to decide who is worth an outreach slot |
| **Lead generation** | A CRM-ready export with your own row IDs already attached |
| **Sales / BD research** | Business-domain addresses and role mailboxes, told apart from personal gmail |

### Quickstart

**1.** Enter a hashtag for the niche you want.
**2.** Click **Start**.
**3.** Export. **Every creator you get has an email address.**

```json
{
  "hashtags": ["ugccreator"],
  "maxCreators": 100
}
```

That is the whole thing. Every extraction option is on by default and there is nothing to tune.

### Choose your input path

All three can be combined in one run.

| Path | Use it when | Lookups per delivered email |
|---|---|---|
| `hashtags` ⭐ | **Start here.** Contactable creators in a niche | **~1.2** |
| `keywords` | A specific phrase, or a niche with no obvious tag | ~1.4 on a good term, ~6.4 on a broad one |
| `profiles` / `profileRecords` | You already know exactly who you want | 1.0, and no discovery charge |

**Hashtags are the cheapest way to get emails, and it is not close.** TikTok returns each
creator's bio *during* hashtag discovery, so creators with no email are skipped before we look
them up — you are never charged for them. Keyword search does not return bios, so a creator who
turns out to have no email has already cost a lookup.

Good hashtags: `#ugccreator`, `#ugccommunity`, `#smallbusinesscheck`,
`#contentcreator`, or whatever tag your niche actually uses.

**If you use keywords, be specific.** Measured on this actor's own benchmark, a niche term like
"ugc creator" returns a **72%** contact rate — the best of any input. A broad name-like term such
as "swift" returns **13%**, because it surfaces fan accounts rather than businesses. The term
matters more than any setting.

Supplying your own profiles avoids the discovery charge entirely, but you have to know the names
already — and a hand-built list measured a **41%** contact rate against 66–72% for discovery.
Search finds contactable people better than memory does.

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `profiles` | string\[] | `[]` | TikTok profile URLs or @usernames |
| `profileRecords` | object\[] | `[]` | Profiles **with your own row IDs** — `{"profile": "@creator", "externalId": "your-id"}`. Every result carries the `externalId` back, so you can join to your CRM without matching on username. Works alongside `profiles` |
| `keywords` | string\[] | `[]` | Keywords to discover creators (e.g. "ugc creator skincare") |
| `hashtags` | string\[] | `[]` | Hashtags to discover creators from |
| `maxCreators` | integer | `500` | Absolute maximum creator records for the whole run |
| `maxCreatorsPerSource` | integer | `50` | Cap on creators from **each** keyword and **each** hashtag, so one broad term cannot consume the run. Applies per source, not in total. No unlimited setting |
| `searchRegion` | string | `""` | Biases keyword discovery towards creators ranking in one country's Google results. **This is where Google searches from, not a verified creator location** — TikTok does not publish creator location. Affects keywords only |
| `skipUsernames` | string\[] | `[]` | Creators you already have. **Skipped before any lookup, so they cost nothing and are never charged.** Paste usernames from a previous run to enrich only what is new |
| `emailDomainAllowlist` | string\[] | `[]` | Only keep emails on these domains (subdomains match). Empty = any domain |
| `emailDomainBlocklist` | string\[] | `[]` | Drop emails on these domains. Ignored when an allowlist is set |
| `minFollowers` | integer | `0` | Minimum follower count filter |
| `maxFollowers` | integer | `0` | Maximum follower count filter (0 = no limit) |

At least one of `profiles`, `keywords`, or `hashtags` is required.

#### Sample input 1: Keyword discovery

Find creators related to specific keywords. Every creator is returned with its contact fields, so filter the dataset by `primaryEmail` if you only want the contactable ones:

```json
{
  "keywords": ["swift", "taylor"],
  "maxCreators": 50
}
```

#### Sample input 2: Direct profile enrichment with hashtag discovery

Enrich a known list of profiles and discover more creators from hashtags:

```json
{
  "profiles": [
    "@taylorfrankiepaul",
    "@taylor_thatdancer",
    "https://www.tiktok.com/@paigetaylor.s"
  ],
  "hashtags": ["beautytok", "gymtok"],
  "maxCreators": 200,
  "minFollowers": 1000
}
```

### Run limits

Published because you should be able to plan a run before you start it. Anything over a limit is
ignored with a warning in the log — nothing is silently truncated.

| Input | Maximum per run |
|---|---|
| `profiles` + `profileRecords` combined | **1,000** |
| `hashtags` | **50** |
| `keywords` | **50** |
| Creators per keyword or hashtag | **50** each (configurable, 1–500) |

### Output

Each output row represents one creator with normalized contact data and source metadata. Results are available in four dataset views:

[![TikTok Contact Extractor — Output overview table view](https://raw.githubusercontent.com/coregentdevspace/tiktok-contact-extractor-assets/main/tiktok-contact-extractor-output-overview-table-view.png)](https://apify.com/coregent/tiktok-contact-extractor)

#### Overview tab

Key identity fields, primary contacts, lead score, and discovery source.

```json
{
  "username": "taylorfrankiepaul",
  "displayName": "Taylor Paul",
  "profileUrl": "https://www.tiktok.com/@taylorfrankiepaul",
  "isVerified": true,
  "followersCount": 6086356,
  "primaryEmail": "taylorfrankiepaul@select.co",
  "primaryPhone": null,
  "externalUrl": "https://www.ulta.com/p/purity-one-step-meltaway-cleansing-balm-pimprod2056354?sku=2649051",
  "instagramHandle": null,
  "youtubeHandle": null,
  "contactReadiness": "high",
  "leadQualityScore": 60,
  "discoveredVia": ["keyword"],
  "scrapedAt": "2026-03-21T05:01:23.943Z"
}
```

#### Contact Details tab

Full contact data including all emails, phones, handles, and links.

```json
{
  "username": "paigetaylor.s",
  "displayName": "Paige Taylor",
  "bio": "✝️\npaige@insightmedia.com",
  "primaryEmail": "paige@insightmedia.com",
  "allEmails": ["paige@insightmedia.com"],
  "primaryPhone": "68608721562",
  "allPhones": ["68608721562"],
  "externalUrl": "https://linktr.ee/paigetaylor.s",
  "externalDomain": "linktr.ee",
  "instagramHandle": null,
  "youtubeHandle": null,
  "tiktokBioLink": "https://linktr.ee/paigetaylor.s",
  "contactPageUrl": "https://linktr.ee/s/about/trust-center/report/?field86145911=https%3A%2F%2Flinktr.ee%2Fpaigetaylor.s",
  "otherPublicLinks": [
    "https://linktr.ee/s/about/trust-center/report/?field86145911=https%3A%2F%2Flinktr.ee%2Fpaigetaylor.s",
    "https://linktr.ee/connectartech"
  ],
  "followersCount": 6778715,
  "isVerified": false
}
```

#### Lead Quality tab

Confidence scores, readiness levels, quality scores, and business signals.

```json
{
  "username": "taylor_thatdancer",
  "displayName": "taylor pierce 🖤🤖",
  "followersCount": 5295218,
  "primaryEmail": "nickyahid1@gmail.com",
  "emailConfidence": 0.8,
  "primaryPhone": null,
  "phoneConfidence": null,
  "contactReadiness": "high",
  "contactReadinessReason": "Public email and website found",
  "leadQualityScore": 60,
  "leadQualityReason": "+25 email, +10 website, +10 high followers, +5 verified, +10 business profile",
  "isBusinessLikeProfile": true,
  "isLikelyCreatorLead": true,
  "isVerified": true
}
```

#### Source Attribution tab

Where each contact was found, website visit status, and discovery lineage.

```json
{
  "username": "lojaswift",
  "primaryEmail": null,
  "emailSource": null,
  "primaryPhone": "68608721562",
  "phoneSource": "website_homepage",
  "instagramHandle": null,
  "instagramSource": null,
  "youtubeHandle": "andywso",
  "youtubeSource": "website_homepage",
  "contactSourceSummary": "Phone found on website. YouTube: andywso",
  "bioContainsEmail": false,
  "bioContainsPhone": false,
  "websiteContainsEmail": false,
  "websiteContainsPhone": true,
  "websiteVisited": true,
  "websiteVisitStatus": "success",
  "discoveredVia": ["keyword"],
  "sourceProfiles": [],
  "sourceKeywords": ["swift"],
  "sourceHashtags": []
}
```

#### Full output (all fields)

Each record contains 47+ fields across identity, stats, contact, source attribution, confidence, lineage, and metadata:

```json
{
  "username": "taylor_pfeffer",
  "profileUrl": "https://www.tiktok.com/@taylor_pfeffer",
  "userId": "6532052352982056962",
  "displayName": "taylor",
  "bio": "be kind \n✨taylorxhairstyles✨\n💌pfeffertaylor@gmail.com💌",
  "profilePictureUrl": "https://p16-common-sign.tiktokcdn-us.com/...",
  "isVerified": false,
  "accountPrivate": false,
  "followersCount": 2562922,
  "followingCount": 139,
  "likesCount": 80529449,
  "videosCount": 1396,
  "primaryEmail": "pfeffertaylor@gmail.com",
  "allEmails": ["pfeffertaylor@gmail.com"],
  "primaryPhone": "68608721562",
  "allPhones": ["68608721562"],
  "externalUrl": "https://linktr.ee/taylor_pfeffer",
  "externalDomain": "linktr.ee",
  "instagramHandle": null,
  "youtubeHandle": null,
  "tiktokBioLink": "https://linktr.ee/taylor_pfeffer",
  "contactPageUrl": "https://linktr.ee/s/about/trust-center/report/?field86145911=https%3A%2F%2Flinktr.ee%2Ftaylor_pfeffer",
  "contactFormUrl": null,
  "otherPublicLinks": [
    "https://linktr.ee/s/about/trust-center/report/?field86145911=https%3A%2F%2Flinktr.ee%2Ftaylor_pfeffer"
  ],
  "emailSource": "bio",
  "phoneSource": "website_homepage",
  "instagramSource": null,
  "youtubeSource": null,
  "contactSourceSummary": "Email found in bio. Phone found on website",
  "bioContainsEmail": true,
  "bioContainsPhone": false,
  "websiteContainsEmail": false,
  "websiteContainsPhone": true,
  "websiteVisited": true,
  "websiteVisitStatus": "success",
  "emailConfidence": 0.75,
  "phoneConfidence": 0.7,
  "contactReadiness": "high",
  "contactReadinessReason": "Public email and website found",
  "leadQualityScore": 60,
  "leadQualityReason": "+25 email, +15 phone, +10 website, +10 high followers",
  "isBusinessLikeProfile": false,
  "isLikelyCreatorLead": true,
  "discoveredVia": ["keyword"],
  "sourceProfiles": [],
  "sourceKeywords": ["taylor"],
  "sourceHashtags": [],
  "discoveryCount": 1,
  "scrapedAt": "2026-03-21T05:01:40.504Z",
  "isPartial": false,
  "errorType": null,
  "errorMessage": null
}
```

### Output fields

#### Creator identity fields

| Field | Description |
|-------|-------------|
| `username` | TikTok username |
| `profileUrl` | Full profile URL |
| `userId` | TikTok internal user ID (if available) |
| `displayName` | Display name / nickname |
| `bio` | Full bio text |
| `profilePictureUrl` | Avatar URL |
| `isVerified` | Verified badge status |
| `accountPrivate` | Whether the account is private |

#### Public stats fields

| Field | Description |
|-------|-------------|
| `followersCount` | Follower count |
| `followingCount` | Following count |
| `likesCount` | Total likes/hearts received |
| `videosCount` | Total videos posted |

#### Contact fields

| Field | Description |
|-------|-------------|
| `primaryEmail` | Best email found. A personal mailbox is preferred over a role account when both exist |
| `allEmails` | All unique emails found across sources |
| `primaryPhone` | Best phone number found |
| `allPhones` | All unique phone numbers found |
| `externalUrl` | Bio link URL |
| `externalDomain` | Domain of bio link |
| `instagramHandle` | Instagram username |
| `youtubeHandle` | YouTube handle |
| `xHandle` | X (Twitter) handle |
| `threadsHandle` | Threads handle |
| `instagramProfileUrl` | Clickable Instagram profile URL |
| `youtubeProfileUrl` | Clickable YouTube profile URL |
| `xProfileUrl` | Clickable X profile URL |
| `threadsProfileUrl` | Clickable Threads profile URL |
| `tiktokBioLink` | Raw bio link from TikTok profile |
| `contactPageUrl` | Contact/about page URL from website |
| `contactFormUrl` | URL of a page carrying a real contact form (a message box or an email field — search and newsletter forms do not count) |
| `emailSourceUrl` | The exact page an email was found on. Empty when the email came from the bio |
| `otherPublicLinks` | Other URLs found in bio or website |

#### Contact classification fields

Derived from the primary email at no extra cost. They exist so you can sort a lead list by
usefulness in a spreadsheet without writing a formula.

| Field | Description |
|-------|-------------|
| `emailDomain` | Domain part of `primaryEmail` |
| `emailDomainClass` | `business` / `free` / `disposable` / `unknown` |
| `emailIsRoleAccount` | True for `info@`, `booking@`, `press@` and similar shared mailboxes |
| `emailSyntaxValid` | Syntax check only. **Not** mailbox verification — no mail server is contacted |
| `contactFound` | True when at least one email or phone was delivered |
| `emailFound` / `phoneFound` | Per-channel presence flags |
| `contactCount` | Number of distinct contact channels found (email, phone, website, Instagram, YouTube, contact form) |

#### Source attribution fields

| Field | Description |
|-------|-------------|
| `emailSource` | Where the email was found: `bio`, `website_homepage`, `bio_and_website` |
| `phoneSource` | Where the phone was found |
| `instagramSource` | Where Instagram handle was found |
| `youtubeSource` | Where YouTube handle was found |
| `contactSourceSummary` | Human-readable summary of all contact sources |
| `bioContainsEmail` | Whether bio text contained an email |
| `bioContainsPhone` | Whether bio text contained a phone number |
| `websiteContainsEmail` | Whether linked website contained an email |
| `websiteContainsPhone` | Whether linked website contained a phone |
| `websiteVisited` | Whether the bio link was visited |
| `websiteVisitStatus` | `success` or `error` |

#### Confidence and quality fields

| Field | Description |
|-------|-------------|
| `emailConfidence` | Email confidence score (0.0–1.0) |
| `phoneConfidence` | Phone confidence score (0.0–1.0) |
| `contactReadiness` | `high`, `medium`, `low`, or `none` |
| `contactReadinessReason` | Why this readiness level was assigned |
| `leadQualityScore` | Lead quality score (0–100) |
| `leadQualityReason` | Scoring breakdown |
| `isBusinessLikeProfile` | Whether bio contains business/outreach keywords |
| `isLikelyCreatorLead` | Whether creator has contacts + meaningful following |

#### Discovery lineage fields

| Field | Description |
|-------|-------------|
| `discoveredVia` | How this creator was found: `profile_input`, `keyword`, `hashtag` |
| `sourceProfiles` | Which profile inputs led to this creator |
| `sourceKeywords` | Which keyword searches led to this creator |
| `sourceHashtags` | Which hashtag searches led to this creator |
| `discoveryCount` | Number of input sources that found this creator |
| `externalId` | Your own row ID, echoed back from `profileRecords`. Empty if you did not supply one |
| `inputIndex` | Position of this creator in your input list |
| `resultStatus` | Present and set to `filtered_out` on stub rows for creators your follower range excluded. Absent on normal rows |
| `filteredReason` | Why a stub row was filtered out |

#### Run metadata fields

| Field | Description |
|-------|-------------|
| `resultStatus` | What happened for this creator — see below |
| `retryable` | Whether a re-run could plausibly do better |
| `scrapedAt` | ISO 8601 timestamp |
| `isPartial` | Whether extraction was incomplete |
| `errorType` | Error type if failed |
| `errorMessage` | Error message if failed |

#### Result status values

One readable column instead of reasoning over `isPartial`, `errorType` and null checks.
`retryable` is the half that matters for automation: it separates "this creator genuinely
publishes nothing" from "we could not finish looking".

| `resultStatus` | Meaning | `retryable` |
|---|---|---|
| `contact_found` | At least one email or phone was delivered | `false` |
| `no_contact_found` | Profile read successfully; nothing public to find | `false` |
| `partial_contact_search` | Bio parsed, but the linked website could not be read | `true` |

### Pricing

Pay-per-event. You pay for results, never for compute time.

**The exact rate for each event is on this actor's pricing page in the Apify Console.** That page
is the only authoritative source — a number copied into a README goes stale the first time a price
changes, and a stale price is worse than no price.

What this section does tell you, and what will not go stale, is **exactly which events a run fires**,
so you can multiply by the posted rate and know your bill before you press Start.

#### The three events

| Event | Fires |
|---|---|
| `creator-profile-result` | Once for **every profile evaluated** |
| `creator-discovery-result` | **In addition**, only for a creator we found for you from a keyword or hashtag |
| `contact-found` | **In addition**, only when the row carries at least one email or phone number |

A row with no contact details costs you strictly less than a row with one. That is the whole point
of splitting the charge: you should not pay the same for a miss as for a hit.

**They stack.** These are not alternatives — up to three can apply to one creator:

| That creator | profile | discovery | contact |
|---|:---:|:---:|:---:|
| You pasted them; no contact details found | ✓ | — | — |
| You pasted them; we found an email | ✓ | — | ✓ |
| We found them via keyword/hashtag; no contact details | ✓ | ✓ | — |
| We found them via keyword/hashtag; we found an email | ✓ | ✓ | ✓ |

The last row is the common case on a hashtag run, and it is the most you can ever be charged for
a single creator. The run log prints the count as it goes — `Charged 1 profile, 1 discovery,
1 contact event(s)` — so you can reconcile the invoice against the log line by line.

#### Two worked examples

**A — 1,000 creators you supplied, 20% of them contactable**

| Event | Count |
|---|---|
| `creator-profile-result` | 1,000 |
| `creator-discovery-result` | 0 — you supplied them, we found nobody |
| `contact-found` | 200 |

**B — 1,000 creators discovered from 20 keywords, 20% of them contactable**

| Event | Count |
|---|---|
| `creator-profile-result` | 1,000 |
| `creator-discovery-result` | 1,000 — every one of them had to be found |
| `contact-found` | 200 |

#### What you are never charged for

- **Profiles we could not load.** A failure costs you nothing.
- **Usernames that do not exist.** A deleted or misspelled handle produces no row at all, so
  there is nothing to charge. It is not returned as an empty creator.
- **Creators on your `skipUsernames` list.** They are skipped before any lookup happens, so they
  cost us nothing and we charge you nothing.
- **Any crawling.** Visiting a bio link, a Linktree, and a contact page is included in the profile
  event. There is no per-page charge and no way to run up a bill by enabling something.
- **Runs with no input configured.**

#### What you are charged for, and how to see it

**Filters reduce output, not scan cost.** A creator excluded by your follower range was already
fetched before we knew their follower count, so that creator costs the same as any other and is
charged `creator-profile-result`.

So that this is never a surprise on an invoice, **every charged profile appears in your dataset**.
Filtered ones arrive as a stub row with `resultStatus: "filtered_out"` and no contact columns.
Count the rows and you have counted the charges. Filter that column out in the dataset view and you
have your clean list.

**Spending limit:** if yours is reached, the actor stops cleanly rather than running up a bill.

#### Free plan

The actor works on the Apify Free plan. Every feature is available and nothing is degraded.

One limit applies: **a Free-plan run stops after 100 lookups that return no creator.** Those are
deleted, renamed or misspelled usernames. A normal creator list contains very few, so you are
unlikely to meet this; a list of bad usernames meets it quickly.

If it happens: everything already found is in your dataset and unaffected, you are told in the run
log and status, the run ends cleanly rather than erroring, and **you are never charged for a**
**lookup that found nothing** — on any plan. Any paid Apify plan removes the limit.

The reason is narrow and worth stating plainly: a lookup for a handle that does not exist still
costs us an upstream API call, and because it produces no row it consumes none of the Free plan's
monthly credit. It is the one cost with nothing else bounding it.

### Cost controls

| Control | What it does |
|---|---|
| `skipUsernames` | Creators you already have. Skipped **before any lookup**, so they are free. Paste last run's usernames to enrich only what is new |
| `maxCreatorsPerSource` | Stops one broad keyword or hashtag consuming the whole run. Default 50, applied to each source separately |
| `maxCreators` | Absolute ceiling for the run. Default 500, max 5,000 |
| `emailDomainBlocklist` | Drops addresses you do not want before they reach `contact-found` |

The single biggest saving is `skipUsernames` on a repeat run. It is the one control that removes
work rather than discarding it after the fact.

### Result status guide

Every row carries `resultStatus`. Four values, and what to do about each:

| `resultStatus` | Meaning | What to do |
|---|---|---|
| `contact_found` | An email or phone was found | Use it |
| `no_contact_found` | We looked everywhere available and found nothing public | Nothing to do — this creator has no public contact route |
| `partial_contact_search` | Their website could not be reached, so the search was incomplete | Worth a re-run later; the site may have been temporarily down |
| `filtered_out` | Evaluated and charged, then excluded by your follower range | Filter these out of your export |

When a website was visited, `websiteVisitStatus` says how it went:

| `websiteVisitStatus` | Meaning |
|---|---|
| `success` | The site was fetched and mined |
| `link_hub` | The bio link was a Linktree-style hub; we read its destinations |
| `link_hub_followed` | We identified the creator's own site behind the hub and mined that too |
| `skipped_platform` | The bio link pointed at YouTube, Instagram or similar. **Deliberately not mined** — see Limitations |
| `error` | The site did not respond |

### Contact extraction logic

#### Bio extraction

The actor parses public TikTok bios for:

- Email addresses (regex-based)
- Phone numbers (various formats)
- Instagram handles (`ig: @handle`, `instagram.com/handle`)
- YouTube handles (`yt: @handle`, `youtube.com/@handle`)
- Website URLs
- Business/outreach hints (collabs, partnerships, inquiries, etc.)

#### Link-in-bio extraction

The actor visits each creator's public bio link and extracts:

- Email addresses from page text and HTML
- Phone numbers
- Contact/about page links (and visits them too)
- Instagram and YouTube links
- Contact form detection

#### Confidence scoring

Confidence **increases** when:

- A contact value appears in multiple sources (bio + website)
- An email uses a brand/custom domain
- The bio contains business context keywords

Confidence **decreases** when:

- The email uses a disposable domain
- The value is ambiguous or partial
- Only a single low-quality source is available

Confidence scores are **heuristics, not guarantees**. They indicate relative trustworthiness.

#### Lead quality scoring

Lead quality is scored 0–100 based on:

- Having an email (+25), phone (+15), website (+10)
- Multi-source confirmation (email in bio AND website)
- Social presence (Instagram, YouTube)
- Follower count and verification
- Business-oriented bio

### Confidence scores are not verification

> **Read this before you send anything.** `emailConfidence` and `phoneConfidence` are
> **heuristics about where a value came from**, not checks that it works. A score of 0.95 means
> "found in a place that is usually reliable, on a business domain, confirmed in more than one
> source". It does **not** mean the mailbox exists, is monitored, or will accept your message.

No mailbox is ever contacted, no SMTP check is performed, and no third-party verification service
is called. If you need deliverability guarantees, run the exported addresses through a dedicated
verifier before a campaign. What this actor gives you is a ranked, sourced starting list — with
`emailSourceUrl` pointing at the exact page an address came from, so you can check any row
yourself in one click.

### Cross-platform contacts

Handles and clickable profile URLs are extracted for four platforms when a creator publishes them:

| Platform | Handle | URL |
|---|---|---|
| Instagram | `instagramHandle` | `instagramProfileUrl` |
| YouTube | `youtubeHandle` | `youtubeProfileUrl` |
| X (Twitter) | `xHandle` | `xProfileUrl` |
| Threads | `threadsHandle` | `threadsProfileUrl` |

These are **read from what the creator already published** in their bio or on their site. Nothing
is looked up on those platforms, no extra request is made, and there is no separate charge.

### Use cases

- **Influencer outreach** — find creators with public emails for sponsorship pitches
- **Lead generation** — build niche-specific lists of contactable creators
- **Creator partnerships** — identify business-ready creators with complete contact info
- **CRM enrichment** — enrich existing creator lists with contact data and quality scores
- **Campaign sourcing** — find outreach-ready creators in target niches
- **Affiliate recruiting** — discover creators open to business inquiries

### Tips and best practices

- **Start with direct profiles** for highest accuracy — the actor visits each profile page
- **Filter the results, not the run** — every creator is returned with its contact fields, so sort or filter the dataset by `primaryEmail`, `contactReadiness` or `leadQualityScore` after the run
- **Use `contactReadiness` and `leadQualityScore`** to prioritise high-confidence leads — both are always returned
- **Check `contactReadiness`** — "high" means strong public contact data
- **Use `leadQualityScore`** to sort and prioritize leads for outreach
- **Start small** — test with a few profiles before running large discovery batches
- **Schedule for monitoring** — run periodically to discover new contactable creators

### Proxy configuration

**There is nothing to configure.** Proxy handling is automatic and has no input field.

Creator profiles are fetched through a hosted API that uses no proxy at all, which covers the
overwhelming majority of every run. Where a proxy is genuinely needed — discovery pages and a
rare fallback path — the actor enables Apify's datacenter proxy on its own.

Apify Residential is never used. It is billed per gigabyte and cannot be selected, requested, or
configured here.

### Important notes

- **All data comes from public sources only.** This actor does not access private or login-only data.
- **Emails and phones are extracted on a best-effort basis.** Not all creators have public contact information.
- **Website-derived contacts may vary by page structure.** Some websites block scraping or use dynamic rendering.
- **Confidence scores are heuristics, not guarantees.** They indicate relative trustworthiness, not verified accuracy.
- **Some creators will have no public contact information.** They are still returned, with empty contact fields and a `contactReadiness` of `none`, so you can filter or sort them out in the dataset view or your own export.
- **This actor does not send messages, validate inboxes, or verify contacts.** It extracts and scores publicly available data.

### Integrations

- **Google Sheets** — export contact records to spreadsheets for team use
- **CRM systems** — flat output is ready for direct CRM import
- **Airtable** — structured records map cleanly to Airtable bases
- **Webhooks** — trigger outreach workflows when a run completes
- **API** — access results programmatically via the Apify API

### What this actor is NOT

Stated plainly so you can rule it out in ten seconds rather than after a run:

- **Not a private-data tool.** Everything comes from pages any logged-out visitor can see. No
  login, no cookies, no scraping of anything behind a follow or a DM.
- **Not an email verifier.** See the callout above.
- **Not an outreach sender.** It finds contact routes; it never messages anyone.
- **Not a general TikTok scraper.** It does not collect videos, comments, sounds, view counts or
  engagement history. If you want those, this is the wrong actor.
- **Not a location filter.** `countryHint` was removed precisely because it read like one and
  was not — TikTok does not publish creator location.

### Limitations

- Contact extraction depends on what creators make public. Many creators have no public email or phone.
- **Bio links pointing at another platform are not mined for contacts.** If a creator's link goes to
  YouTube, Instagram, Linktree, Amazon or a similar site, the page is not scanned for contact
  details — those belong to the platform, not the creator. Reporting a corporate switchboard as a
  creator's phone number is worse than reporting nothing. `websiteVisitStatus` reads
  `skipped_platform` in that case.
- **A link-in-bio hub is read, but we will not guess which link is the creator's.** Linktree,
  Beacons, Stan.store and similar pages are parsed for the creator's own destinations. Those pages
  also carry sponsors and affiliate links, and nothing in the markup says which is which — so we
  follow a destination only when it matches the creator's handle, or when it is the only candidate.
  Otherwise we return no website rather than a stranger's. An early version ranked by URL length
  and confidently returned a **sponsor's** site for two different creators; declining is better.
- Website visits may fail for sites with aggressive bot protection.
- Phone number parsing is best-effort and may include false positives from numeric strings in bios.
- Keyword and hashtag discovery depend on TikTok search results, which vary by region and time.
- The actor does not bypass any TikTok restrictions or access private content.

### FAQ

**How many creators can I extract contacts for?**
Up to 5,000 per run via `maxCreators`. Actual results depend on how many creators are discoverable from your inputs.

**Why are some fields null?**
Fields like `primaryEmail`, `primaryPhone`, and `instagramHandle` are only populated when public data is found. Not all creators share contact information publicly.

**What is the difference between `primaryEmail` and `allEmails`?**
`primaryEmail` is the first/best email found. `allEmails` contains all unique emails found across bio and website sources.

**What does `contactReadiness: "high"` mean?**
It means the creator has strong public contact data — typically an email plus a website or multiple confirmed sources.

**Does this actor validate email addresses?**
No. It extracts publicly visible email addresses and scores their confidence based on source quality. It does not verify that an inbox exists.

**What happens if a profile is private or unavailable?**
A partial result is emitted with `isPartial: true` and an error message, so you can identify failures without the whole run breaking.

**What does a run actually cost me?**
Rates are on the pricing page in the Console. The Pricing section above shows exactly which events
fire for a given run, so you can work out the bill before you press Start.

**Why am I charged for creators that were filtered out?**
Because filtering happens after we fetch the profile — we cannot know a creator's follower count
until we have looked them up, and the lookup is the cost. Rather than hide that, every charged
profile appears in your dataset, filtered ones as a `filtered_out` stub. Every charge has a row.

**How do I avoid paying twice for the same creator?**
Put last run's usernames in `skipUsernames`. They are skipped before any lookup, so they cost
nothing at all.

**Can I join the results back to my own list?**
Yes. Use `profileRecords` instead of `profiles` and give each entry an `externalId`. It comes
back on every row, stubs included, so you can join on your own key instead of on username.

**A creator's bio links to Linktree. Does that work?**
Yes. Link-in-bio hubs are read for the creator's own destinations, and if we can identify their
real website behind the hub we follow it and mine that too. If we cannot tell which link is theirs
and which is a sponsor's, we deliberately return nothing rather than guess — see Limitations.

**Is this legal?**
It collects only publicly visible information. How you then use that information is regulated —
see Legal & compliance below.

### Legal & compliance

This actor collects **only publicly visible information** — the same things any logged-out visitor
can read on a profile page or a creator's own website. It performs no login, uses no cookies, and
accesses nothing private.

Collecting a contact detail and being allowed to use it are different questions, and the second one
is yours to answer:

- **GDPR / UK GDPR** treat a work email as personal data. You need a lawful basis to process it,
  and legitimate interest for B2B outreach carries conditions — including telling people where you
  got their details and honouring opt-outs.
- **CAN-SPAM, PECR and equivalents** govern what you may send once you have an address.
- **A published address is not consent.** A creator listing an email for brand enquiries has not
  agreed to a bulk campaign.

The `emailSourceUrl` field exists partly for this: it records the exact page each address came
from, which is what a provenance question looks like in practice.

This is not legal advice. If you are running outreach at scale, take your own.

### Related actors

- **TikTok Profile Scraper** — full profile and stats data, when contacts are not the point
- **TikTok Hashtag Scraper** — video-level data from a hashtag
- **Google Maps Website Contact Extractor** — the same contact-extraction idea for local businesses

### Changelog

**Last verified against live TikTok: 2026-08-27.**

| Date | Change |
|---|---|
| 2026-08-27 | Three-event pricing: a row without contacts no longer costs the same as one with |
| 2026-08-27 | Every charged profile now appears in the dataset, filtered ones as a `filtered_out` stub |
| 2026-08-27 | Added `skipUsernames` — skip creators you already have, before any lookup, for free |
| 2026-08-27 | Added per-keyword and per-hashtag caps so one broad input cannot consume a run |
| 2026-08-27 | Added `profileRecords` with `externalId` for CRM join-back |
| 2026-08-27 | Link-in-bio hubs (Linktree, Beacons, Stan.store and similar) are now read, not skipped |
| 2026-08-27 | Contact-page crawl now continues while any channel is missing, not just emails |
| 2026-08-27 | Added X and Threads handles, plus clickable profile URLs for all four platforms |
| 2026-08-27 | Added `emailSourceUrl` — the exact page an address was found on |
| 2026-08-27 | Simplified the input from 24 fields to 12; everything removed was already the default |

# Actor input Schema

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

TikTok profile URLs or usernames to extract contacts from. Accepts @username or full profile URLs. FREE PLAN NOTE: on the Apify Free plan a run stops after 100 lookups that return no creator (deleted or misspelled usernames). Everything found before that is kept and you are never charged for a lookup that found nothing. Any paid plan removes the limit.

## `profileRecords` (type: `array`):

Enrich a CRM list and get your own row IDs back. Each entry is {"profile": "@creator", "externalId": "your-row-id"}, and every result carries that externalId so you can join the output to your source list without matching on username. Use this OR the plain Profiles field — both work, and both can be used together.

## `keywords` (type: `array`):

Keywords or niche terms to discover creators (e.g. 'ugc creator skincare', 'fitness coach').

## `hashtags` (type: `array`):

Hashtags to discover creators from. Accepts plain text, #tag, or full TikTok tag URLs.

## `maxCreatorsPerSource` (type: `integer`):

Maximum creators LOOKED UP from each keyword and each hashtag. Applies per source, not in total — five hashtags at 50 means up to 250 lookups. With Only Creators With An Email on, the number you receive is lower than this, because creators without an email are filtered out; on hashtags most are skipped for free before the lookup, on keywords they are not. Raise it to receive more. Profiles you supply directly are not capped. There is no unlimited setting.

## `searchRegion` (type: `string`):

Biases keyword discovery towards creators who rank in a particular country's Google results — useful when you want UK or German creators rather than whoever ranks globally. IMPORTANT: this is the region Google searches FROM, NOT a verified creator location. TikTok does not publish creator location and we do not guess at it, so a creator here may live anywhere; this only changes which results rank highest. Affects KEYWORDS only — hashtags and direct profiles ignore it. Leave on Default unless you specifically want regional bias.

## `maxCreators` (type: `integer`):

Maximum number of creator contact records RETURNED across all inputs. Note: creators removed by the filters below were still scanned, so filters reduce output, not scan cost.

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

Only include creators with at least this many followers.

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

Only include creators with at most this many followers. Set 0 for no limit.

## `skipUsernames` (type: `array`):

Usernames you already hold. They are skipped BEFORE any lookup happens, so they cost nothing and are never charged. Paste the usernames from a previous run to enrich only what is new. Accepts creator, @creator or a full TikTok profile URL.

## `emailDomainAllowlist` (type: `array`):

Only keep emails on these domains (e.g. gmail.com, brand.com). Subdomains match too. Leave empty to accept any domain. This is filtering, not verification — no mailbox is contacted.

## `emailDomainBlocklist` (type: `array`):

Drop emails on these domains. Ignored when an allowlist is set. Useful for stripping noreply/no-reply and theme or CDN addresses.

## Actor input object example

```json
{
  "profiles": [],
  "profileRecords": [],
  "keywords": [],
  "hashtags": [],
  "maxCreatorsPerSource": 50,
  "searchRegion": "",
  "maxCreators": 500,
  "minFollowers": 0,
  "maxFollowers": 0,
  "skipUsernames": [],
  "emailDomainAllowlist": [],
  "emailDomainBlocklist": []
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `contacts` (type: `string`):

No description

## `leads` (type: `string`):

No description

## `sources` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/tiktok-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("coregent/tiktok-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 '{}' |
apify call coregent/tiktok-contact-extractor --silent --output-dataset

```

## MCP server setup

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