# Social Profile Finder - $0.90 per 1,000 Profiles (`dami_studio/social-profile-finder`) Actor

There is no registry mapping a person or a company to their handles, so this goes looking: X, Instagram, LinkedIn, Facebook, TikTok, YouTube and GitHub, from a name, domain, email or @handle. Each row carries the handle, display name and a confidence score. $0.90 per 1,000.

- **URL**: https://apify.com/dami\_studio/social-profile-finder.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 6 total users, 4 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 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.

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

## Social Profile Finder

You rarely have the full picture of a subject, just one clue. A name, a company, a website domain, an email address, an @handle: any one of those is enough to start from here. What comes back is the public profiles that belong to that subject on X (Twitter), Instagram, LinkedIn, Facebook, TikTok, YouTube and GitHub.

Every row carries the profile URL, the handle, the display name on the account, and a confidence score with the evidence behind it.

No account, no cookies, no login, no browser.

- One input box takes all of it. "Patagonia", "patagonia.com", "press@patagonia.com", "@patagonia" or a profile URL.
- Seven networks per lookup, each checked on that network's own public surface, so a match is confirmed rather than guessed.
- Every row scores the match from 0 to 0.99 and lists the evidence that produced the score, so you can audit it instead of trusting it.
- Set a confidence floor and weak matches are never written, never charged and never land in your CRM.
- Empty input returns one labelled sample row, free.

### Price

**$0.90 per 1,000 profiles.** Plus **$0.001 per run** as the platform start fee, billed per gigabyte of run memory, so exactly that on the default 1 GB.

No volume tiers, no minimum spend, no subscription.

| Profiles | Total |
|---|---|
| 100 | $0.091 |
| 1,000 | $0.901 |
| 10,000 | $9.001 |
| 100,000 | $90.001 |

#### What gets charged

- One `profile-found` event per profile row written to the dataset. Nothing else is metered per row.
- Free: the sample row an empty run returns, and every diagnostic row. They all carry `"charged": false`.
- Lookups that find nothing produce an uncharged diagnostic row explaining why.
- Profiles scoring below your confidence floor are dropped before they're charged.
- Duplicate profiles found twice in the same run are collapsed before they're charged.
- A run that finds nothing costs the start fee and nothing else.
- Rows never leave the dataset without a charge and are never charged without a row. The billed event is a named one, so nothing is quietly attached to `apify-default-dataset-item`.

### Input

```json
{
  "queries": [
    "patagonia.com",
    "Marques Brownlee",
    "press@patagonia.com",
    "@MrBeast",
    "https://github.com/torvalds"
  ],
  "networks": ["x", "instagram", "linkedin", "facebook", "tiktok", "youtube", "github"],
  "minConfidence": 0.65,
  "maxItems": 50
}
```

| Field | What it does |
|---|---|
| `queries` | What to look up, one entry per line. A person or company name, a domain, an email address, an @handle, or a link to a profile you already have. Up to 50 per run. You can also pass objects like `{"name":"Patagonia","domain":"patagonia.com"}` when you have more than one clue about the same subject. Combining them raises the confidence of the result. |
| `networks` | Which networks to check. Leave empty for all seven. Dropping the ones you don't need makes each lookup faster and cheaper, because a network that isn't checked is never charged for. |
| `minConfidence` | The confidence floor, 0 to 0.99. Default 0.65, which is deliberately strict. Lower it to about 0.5 if you'd rather review weak matches yourself. Raise it to 0.8 for self-declared and badge-verified profiles only. |
| `maxProfilesPerNetwork` | How many profiles to keep per network per lookup. Default 1, the highest-scoring one. Raise it to 2 or 3 for brands that legitimately run several accounts on one network. |
| `maxHandleVariants` | How many handle spellings to try when all you have is a name: "janedoe", "jane.doe", "jane\_doe" and so on. Default 3, maximum 5. More variants means more coverage and more requests. |
| `checkWebsite` | Whether to read the subject's own website for the profiles it links to. Default on, and it's the single strongest signal available, so leave it on unless you only ever pass bare handles. |
| `maxItems` | Total profiles across every lookup in the run. Default 50, hard ceiling 2,000. Keep it low while testing, since you pay per profile. |
| `proxyUrls` | Leave empty. Only for callers who want traffic to leave through proxy servers they already pay for, as `http://user:pass@host:port`. |

Run it with empty input and you get one labelled sample row, free.

### Output

One row per profile. This is a real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "profile",
  "query": "patagonia.com",
  "subject": "patagonia",
  "subjectDomain": "patagonia.com",
  "network": "instagram",
  "networkLabel": "Instagram",
  "profileUrl": "https://www.instagram.com/patagonia/",
  "handle": "patagonia",
  "displayName": "Patagonia",
  "matchConfidence": 0.95,
  "confidenceLabel": "high",
  "matchEvidence": "verified-on-network | linked-from-own-website | structured-data-sameas | display-name-matches | platform-verified-badge | established-audience",
  "source": "website-link",
  "followers": 5498775,
  "platformVerified": true,
  "bio": "We're in business to save our home planet.",
  "profileWebsite": "http://sprout.link/patagonia/",
  "verifiedLive": true,
  "scrapedAt": "2026-08-16T15:45:01.719Z"
}
```

#### Field notes

- `network` is one of `x`, `instagram`, `linkedin`, `facebook`, `tiktok`, `youtube`, `github`. Lower case and stable, so it's safe to switch on in code.
- `profileUrl` is the canonical, openable profile address. For a YouTube channel found by link this is the `/channel/UC...` form, which doesn't break when a creator changes their handle.
- `handle` is the account name as that network spells it, taken from the network's own answer rather than your input, so capitalisation and any redirect are already resolved.
- `displayName` is the name shown on the account. Null when the network won't hand it over, which is honest reporting rather than a scraping failure. The confidence score already accounts for it.
- `matchConfidence` runs 0 to 0.99. Never 1.0, because this is public evidence rather than proof of identity.
- `confidenceLabel` is `high` at 0.8 and above, `medium` from 0.6, `low` below that. A convenience for spreadsheet filters.
- `matchEvidence` lists the named reasons behind the score, pipe separated. This is the field to read when a match surprises you.
- `source` is `website-link` if the subject's own site published it, `handle-probe` if a candidate handle was confirmed on the network, `cross-link` if another confirmed profile pointed at it.
- `followers` is the audience size where the network publishes it, otherwise null. TikTok, YouTube and LinkedIn member profiles frequently return null here.
- `platformVerified` is true when the network itself marks the account as verified. Null means the network didn't say, which isn't the same as false.
- `verifiedLive` is true when the profile was confirmed against the network during this run. False only appears for a link the subject published on their own site that the network wouldn't confirm at that moment.
- `subjectDomain` is the website the lookup was anchored to. You either passed it directly or it came out of an email address.

Real rows carry `"charged": true`. Sample rows carry `"_sample": true`, diagnostic rows carry `"_diagnostic": true` and an `errorCode`. Neither is billed.

### How it works

Each entry is classified first: an email, a domain, a handle, a profile link or a plain name. Guessing that wrong wastes the whole lookup.

If a website is known, it gets read for the profiles it links to, including the machine-readable `sameAs` block many sites publish. A profile the subject published themselves is the strongest evidence there is.

Candidate handles are then built from what you gave: the domain label, the email local part, the name run together, and a few common spellings of it. Every candidate is checked against that network's own public surface, the endpoint the site itself uses to render a profile, so an account is confirmed to exist and its display name is read straight from the network. Nothing is reported on a guess.

The confirmed profiles are scored against each other: display name against the name you asked for, the profile's own outbound link against your domain, the network's verification badge, the audience size, and whether one confirmed profile points at another. Anything below your floor is dropped before it's written.

Requests leave through a rotating pool of addresses, so a per-address rate limit is answered by moving rather than waiting.

### How the confidence score is built

The score is additive and every term is named in `matchEvidence`, because a number nobody can audit is a number nobody should trust. A profile starts at zero and collects:

| Evidence | Weight | What it means |
|---|---|---|
| `verified-on-network` | +0.30 | the network's own endpoint returned this profile during the run |
| `linked-from-own-website` | +0.35 | the subject's website links to it |
| `structured-data-sameas` | +0.05 | and it was in the site's machine-readable profile block, not just a footer icon |
| `explicit-handle` | +0.20 | you supplied this exact handle or profile link |
| `handle-matches-domain` | +0.20 | the handle is the domain label, e.g. `patagonia` for patagonia.com |
| `handle-from-email` | +0.10 | the handle is the local part of the email you gave |
| `handle-matches-name` | +0.10 | the handle is the name with the spaces taken out |
| `display-name-matches` | +0.20 | the name on the account covers the name you asked for |
| `display-name-similar` | +0.10 | it partly covers it |
| `display-name-differs` | -0.15 | both names were readable and they don't match |
| `links-back-to-domain` | +0.15 | the profile's own website field points back at your domain |
| `platform-verified-badge` | +0.10 | the network marks the account as verified |
| `established-audience` | +0.05 | 10,000 followers or more, so not a freshly squatted handle |
| `cross-linked-from-github` | +0.10 | another confirmed profile of the same subject names this handle |
| `not-verified-on-network` | — | the subject's site published this link but the network wouldn't confirm it during the run |

The result is clamped to 0.99.

**Why the default floor is 0.65.** A bare name can only ever earn 0.30 + 0.10 + 0.20 = 0.60, which sits below the default. So a handle that merely exists and merely echoes the name back, the classic squatter or fan account, isn't reported at all. It takes a verification badge, a real audience, a link back to your domain, or the subject's own website vouching for it, before a name-derived guess is shown to you. A wrong profile in a lead list is worse than a blank cell, because the blank cell is honest. Set `minConfidence` to 0.5 if you'd rather review the weak matches yourself.

**The name comparison is asymmetric on purpose.** It measures how much of the name you asked for is accounted for by the name on the account. "NASA" is fully accounted for by an account called "NASA - National Aeronautics and Space Administration", so that's a match. "Marques Brownlee" is only half accounted for by an account called "Marques", so it isn't. During testing that exact rule is what stopped an 87-follower impostor from being reported.

### What people use it for

- Enriching a lead list. Paste a column of company domains and get their X, Instagram, LinkedIn, Facebook, TikTok and YouTube accounts back as rows, ready to join on the domain.
- Vetting an influencer or partner. One handle in, every network they're on out, with follower counts and verification badges next to each.
- Recruiting research. An email address, or a name plus a company domain, resolves to the public professional profiles.
- Brand protection. Search your own brand name and see every account carrying it, then look at the ones your website doesn't link to.
- Filling gaps in a CRM that has a name and an email but no social handles, on a schedule, with a floor high enough that nothing dubious gets written back.
- Due diligence and journalism, where a documented evidence trail per match matters more than volume.

### Reading the output

- **Real rows** carry `"charged": true` and `"recordType": "profile"`. One billed event each.
- **The sample row** carries `"_sample": true` and `"charged": false`. There's exactly one, only when the input had nothing to look up.
- **Diagnostic rows** carry `"_diagnostic": true`, `"charged": false` and an `errorCode`: `NO_RESULTS` when a lookup produced nothing above your floor, `NETWORK` when a network couldn't be reached, `TIME_BUDGET` when time ran out, `RATE_LIMITED` when a network throttled the run. Each carries the `query` it belongs to and a plain-English explanation.

Filter on `charged == true` and you have exactly the rows you paid for.

### Limits

- Seven networks are covered: X (Twitter), Instagram, LinkedIn, Facebook, TikTok, YouTube and GitHub. Nothing else is checked, so a subject who is only on a network outside that list comes back empty.
- A plain name with no domain, email or handle is the hardest input there is. Unless the account carries a verification badge, a real audience or a link back to a website you supplied, a name-derived match won't clear the default floor. Give it a domain or an email whenever you have one.
- Instagram meters its public profile endpoint hard per address. When it refuses, the run falls back to a surface that confirms the account exists but returns no display name. If that's refused too you get an uncharged diagnostic row rather than a guess. Instagram is the least complete of the seven.
- LinkedIn answers member profiles with a bot-check status that means "not now" and "doesn't exist" at the same time. That's never treated as a match and never as a clean absence. It produces a diagnostic row, so LinkedIn member coverage is best-effort while LinkedIn company pages are reliable.
- Facebook coverage is Pages and public profiles that permit embedding. A personal profile with tight privacy settings won't be confirmed.
- YouTube channels found by a `/channel/UC...` link are confirmed and named. A channel with no published videos may come back with a null display name.
- The score is evidence, not proof. A `high` row means several independent public signals agree, not that identity has been established. Treat 0.99 as "very likely" and check anything that matters.
- Sites behind a bot check may refuse to be read. The lookup still runs every handle check, and an uncharged diagnostic row records that the website couldn't be read.
- Follower counts and verification badges are a snapshot at read time.
- Private, suspended, deleted or renamed accounts are reported as absent, because the network reports them as absent.
- Only public information is read. No login, no cookie, no session belonging to anyone.
- Hard ceilings: 50 lookups and 2,000 profiles per run.

### Questions

**What exactly counts as a billable profile?**

One confirmed profile row that cleared your confidence floor. A lookup that returns nothing isn't billed, a profile below your floor isn't billed, and diagnostic rows are never billed. To spend less, raise `minConfidence` or trim the `networks` list.

**Why did a lookup come back with fewer networks than I expected?**

Either the subject isn't on that network under any handle derivable from what you gave, or the match scored below your floor, or the network wouldn't answer. The diagnostic rows name which of the three it was.

**Can I trust a `high` confidence row without checking it?**

For a company with a website, yes in practice. A `high` row there usually means the company's own site links to the profile and the network confirmed both the account and its name. For an individual found from a name alone, read `matchEvidence` first. The field exists so you never have to take the number on faith.

**Do I need an API key or a login for any of the networks?**

No. Everything read here is what a logged-out visitor sees.

**How do I look up a thousand companies?**

Pass up to 50 entries per run and raise `maxItems`, then schedule or queue the rest. Nothing is held between runs, so splitting a list changes nothing except how long each run takes.

**Will the run fail if a network blocks it?**

No. A blocked or throttled network produces an uncharged diagnostic row and the run still finishes as succeeded, with whatever the other networks returned. A failed run would still bill the start fee, which would mean paying to be told something went wrong.

**I have a name and a company domain for the same person. How do I pass both?**

As one object: `{"name":"Jane Doe","domain":"acme.com"}`. Both clues are used for the same subject, and the extra evidence raises the confidence of every match instead of producing two separate lookups.

**Can I use this to find someone's private accounts?**

No, and it isn't built to. It reports accounts that are publicly visible and publicly attributable, with the evidence for each.

# Actor input Schema

## `queries` (type: `array`):

One entry per line. Each can be a person or company name, a website domain, an email address, an @handle, or a link to a profile you already have. Up to 50 per run. When you have more than one clue about the same subject, pass an object instead - {"name":"Patagonia","domain":"patagonia.com"} - and the extra evidence raises the confidence of every match.

## `networks` (type: `array`):

Leave empty to check all seven. Dropping the ones you do not need makes each lookup faster, and a network that is not checked is never charged for.

## `minConfidence` (type: `number`):

Profiles scoring below this are never written and never charged. Same 0 to 0.99 scale as the matchConfidence field in the output. Default 0.65, which is deliberately strict - a handle that merely exists and merely echoes the name back is not reported. Lower it to about 0.5 to review weak matches yourself, or raise it to 0.8 for self-declared and badge-verified profiles only.

## `maxProfilesPerNetwork` (type: `integer`):

How many profiles to keep per network for each lookup. Default 1, the highest-scoring one. Raise it to 2 or 3 for brands that legitimately run several accounts on the same network.

## `maxHandleVariants` (type: `integer`):

How many handle spellings to try when all you have is a name - janedoe, jane.doe, jane\_doe and so on. Default 3, maximum 5. More variants means more coverage and more requests.

## `checkWebsite` (type: `boolean`):

Read the website for the profiles it links to. This is the single strongest signal available, so leave it on unless you only ever pass bare handles. Default on.

## `maxItems` (type: `integer`):

Total profiles to return across every lookup in the run. Default 50, hard ceiling 2,000. Keep it low while you are testing - you pay per profile.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "queries": [
    "patagonia.com",
    "Marques Brownlee",
    "@MrBeast"
  ],
  "minConfidence": 0.65,
  "maxProfilesPerNetwork": 1,
  "maxHandleVariants": 3,
  "checkWebsite": true,
  "maxItems": 50
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset: query, subject, subjectDomain, network, networkLabel, profileUrl, handle, displayName, matchConfidence, confidenceLabel, matchEvidence, source, followers, platformVerified, bio, profileWebsite, verifiedLive. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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 = {
    "queries": [
        "patagonia.com",
        "Marques Brownlee",
        "@MrBeast"
    ],
    "minConfidence": 0.65,
    "maxProfilesPerNetwork": 1,
    "maxHandleVariants": 3,
    "checkWebsite": true,
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/social-profile-finder").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 = {
    "queries": [
        "patagonia.com",
        "Marques Brownlee",
        "@MrBeast",
    ],
    "minConfidence": 0.65,
    "maxProfilesPerNetwork": 1,
    "maxHandleVariants": 3,
    "checkWebsite": True,
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/social-profile-finder").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 '{
  "queries": [
    "patagonia.com",
    "Marques Brownlee",
    "@MrBeast"
  ],
  "minConfidence": 0.65,
  "maxProfilesPerNetwork": 1,
  "maxHandleVariants": 3,
  "checkWebsite": true,
  "maxItems": 50
}' |
apify call dami_studio/social-profile-finder --silent --output-dataset

```

## MCP server setup

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

```

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/1FpfIwkKDvri0PXCR/builds/6RKvUuu0OxcBXjdWY/openapi.json
