# Multi-Site Username Lookup & Profile Link Finder (`vexaloom-labs/multi-site-username-lookup-and-profile-link-finder`) Actor

Search for usernames across 400+ social networks and websites and get a direct profile URL for every site where that username exists. No login or API keys required.

- **URL**: https://apify.com/vexaloom-labs/multi-site-username-lookup-and-profile-link-finder.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Multi-Site Username Lookup & Profile Finder

Find where a username exists across 400+ social networks, developer platforms, forums, gaming sites and marketplaces, and get a real profile URL instead of a guess. Every result carries a per-site status, HTTP code, detection method and verification flag, so a soft-404 page can never masquerade as a profile.

### What this actor does

- Checks up to **100 usernames per run** with concurrent site checks.
- Uses **22 native probes** with exact API/XML/HTML detection for GitHub, GitLab, Steam, Chess.com, Lichess, Hacker News, Codeforces, Docker Hub, Roblox, YouTube, Mastodon, Dev.to, SoundCloud, Instagram, Pinterest, Twitch, X, LeetCode, Keybase, Stack Exchange, Dailymotion and Kick.
- Runs **decoy verification** on the generic long tail: a site that claims a random handle is marked `unverified` and kept out of `links`.
- Exposes site, category, status, HTTP status, detection method and response time for every check, plus an NSFW flag on adult-site rows.
- Extracts profile fields where the site exposes them: display name, bio, avatar, followers, following, posts, join date, location, website and verified badge.
- Hides **NSFW sites by default** and removes ten sites proved unable to distinguish real profiles from missing ones.
- Supports one row per username (legacy-compatible), one row per site, or one row per claimed profile.
- Needs **no login, cookie, API key or proxy**.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `usernames` | array | `["octocat"]` | Handles to look up (required). A leading `@` is ignored. `{?}` expands to `_`, `-` and `.`. |
| `username` | string | – | Optional single handle merged into `usernames`; the array is still required. |
| `sites` | array | `[]` | Names (`GitHub`), domains (`github.com`) or profile URLs. Empty means every site. |
| `categories` | array | `[]` | `social`, `dev`, `gaming`, `forum`, `media`, `business`, `education`, `adult`, `other`. Combined with `sites` as a union. |
| `includeNsfw` | boolean | `false` | Also check adult sites. |
| `includeAvailable` | boolean | `false` | Also emit rows for free, blocked, unknown and illegal checks. |
| `verifyClaims` | boolean | `true` | Run the decoy re-check on generic claimed sites. |
| `enrichProfiles` | boolean | `true` | Add profile fields where available. |
| `outputMode` | string | `perUsername` | `perUsername`, `perSite` or `perHit`. |
| `maxUsernames` | integer | `100` | Cap after wildcard expansion (1–100). |
| `maxSitesPerUsername` | integer | `0` | Check only the top N priority sites; `0` means all (0–500). |

### Output

#### `perUsername` (default, backward compatible)

One row per username. `username` and `links[]` keep their original meaning; `links` now contains only verified claimed profiles. New summary fields and a `profiles[]` array sit beside them.

```json
{
  "username": "octocat",
  "links": ["https://github.com/octocat"],
  "sitesChecked": 428,
  "profilesFound": 1,
  "sitesAvailable": 380,
  "sitesBlocked": 12,
  "sitesUnknown": 15,
  "sitesUnverified": 0,
  "checkedAt": "2026-11-04T12:00:00Z",
  "profiles": [
    {
      "username": "octocat",
      "site": "GitHub",
      "category": "dev",
      "url": "https://github.com/octocat",
      "status": "claimed",
      "httpStatus": 200,
      "detectionMethod": "api_json",
      "verified": true,
      "responseTimeMs": 210,
      "checkedAt": "2026-11-04T12:00:00Z",
      "profile": {
        "userId": "583231",
        "username": "octocat",
        "displayName": "The Octocat",
        "avatarUrl": "https://avatars.githubusercontent.com/u/583231",
        "followers": 18432,
        "following": 9,
        "joinedAt": "2011-01-25T18:44:36Z",
        "location": "San Francisco",
        "website": "https://github.blog",
        "stats": { "repositories": 8, "gists": 8 }
      }
    }
  ]
}
```

#### `perSite`

One row per username × site with `username`, `site`, `category`, `url`, `status`, `httpStatus`, `detectionMethod`, `responseTimeMs` and `checkedAt`, plus `verified` on hit rows, `isNsfw` on adult rows and `profile` when enrichment is on. By default only claimed and unverified rows are emitted; `includeAvailable` adds the rest.

#### `perHit`

One row per claimed and verified profile, with the same shape as `perSite`. With `verifyClaims=false`, claimed rows are emitted without the verification gate.

#### Status vocabulary

| Status | Meaning |
|---|---|
| `claimed` | The site shows a profile for this handle. Native probes and verified generic checks are `verified: true`. |
| `available` | The handle is free on this site. |
| `unverified` | A generic site claimed the handle but could not prove it; kept out of `links`. |
| `waf` | The site returned a bot challenge, 403, 429 or 451. |
| `unknown` | Timeout, 5xx or an unparseable response. |
| `illegal` | The handle does not satisfy the site's own username rule. |

Empty strings, arrays and objects are omitted at every depth.

### Examples

Check a batch of handles across every non-NSFW site:

```json
{
  "usernames": ["johndoe", "janedoe", "octocat"],
  "maxUsernames": 3
}
```

Check only developer and gaming sites, one row per hit:

```json
{
  "usernames": ["octocat"],
  "categories": ["dev", "gaming"],
  "outputMode": "perHit",
  "enrichProfiles": true
}
```

Availability check for a new handle on selected sites:

```json
{
  "usernames": ["mynewbrand2026"],
  "sites": ["GitHub", "Instagram", "X (Twitter)", "Twitch", "Reddit"],
  "includeAvailable": true,
  "outputMode": "perSite",
  "verifyClaims": true
}
```

Fast top-20 scan per handle:

```json
{
  "usernames": ["johndoe"],
  "maxSitesPerUsername": 20,
  "enrichProfiles": false
}
```

### Use cases

- **Brand protection** — find impersonating or parked accounts across networks.
- **OSINT and investigations** — build a verified footprint from one handle.
- **Lead enrichment** — attach public profile URLs and display names to a handle list.
- **Username availability** — see where a new brand handle is free before registering.
- **Recruiting and sourcing** — locate developer profiles on GitHub, GitLab, Codeforces, LeetCode and Stack Exchange.
- **Creator discovery** — compare presence across YouTube, Twitch, Instagram, Pinterest and X.

### FAQ

**How do I know a hit is real?** Native probes use exact API/XML/HTML signals. Generic sites are re-checked with a random decoy handle: if the site claims the decoy too, the original row becomes `unverified` and is excluded from `links`. The `detectionMethod` and `verified` fields tell you exactly which path proved each hit.

**Why are there fewer links than the old version?** The old actor reported soft-404 and WAF pages as profiles. Ten such sites were removed from the catalog, and the remaining generic claims now pass the decoy check. Set `verifyClaims=false` to see the raw claimed rows.

**What does `unverified` mean?** The site returned a claimed signal, but a random handle produced the same signal, so the site cannot distinguish real profiles. The row is kept for transparency (and in `perSite` output) but never enters `links`. `profilesFound` counts claimed plus unverified candidates; `sitesUnverified` and `links` show which are proven.

**Why are NSFW sites missing?** They are excluded unless `includeNsfw=true`. The catalog marks 16 adult entries.

**A site I need is missing. Can I add it?** The catalog is pinned and covers 428 sites. Use `sites` to confirm whether a site is supported; unknown values are logged and ignored. The catalog can be refreshed with `scripts/refresh_catalog.py` when upstream adds sites.

**How does `{?}` work?** It expands to three handles using `_`, `-` and `.`. For example `john{?}doe` checks `john_doe`, `john-doe` and `john.doe`. Expansion happens before `maxUsernames` is applied.

**Do I need cookies, an API key or a proxy?** No. The actor is cookieless and anonymous. Sites that hard-block datacenter IPs or serve a bot challenge are reported honestly as `waf` or `unknown` rather than guessed.

**Why do some sites return `unknown` or `waf`?** Cloudflare, Akamai and similar edges sometimes challenge anonymous requests. Retries handle transient 429/5xx responses, but a persistent challenge stays `waf`, and a timeout or 5xx stays `unknown`.

**Can I check 100 handles in one run?** Yes, `maxUsernames` accepts 1–100. Handles are processed four at a time, with up to 50 site checks in flight across the run. Large scans take longer; narrow them with `sites`, `categories` or `maxSitesPerUsername`. GitHub's anonymous API allows 60 requests/hour, so the tail of a 100-handle GitHub-only batch can fall back to HTML or report `waf`.

**Which fields can be enriched?** Display name, bio, avatar, followers, following, posts, join date, location, website and verified badge, where the site's public response exposes them. Long-tail sites return URL and status only.

### Limitations

- **No login-only data.** Private profiles, follower lists, posts and activity feeds are out of scope.
- **Some sites cannot be detected anonymously.** Reddit, TikTok and Spotify are not shipped as claimed paths; they are either excluded upstream or return `waf`/`unknown`. Sites behind a persistent bot challenge behave the same way.
- **Instagram and X throttle HTML.** From datacenter IPs they can serve a login or generic page, so an existing profile may be reported `available` or `unknown`; no hit is fabricated.
- **Native enrichment covers 22 sites.** The long tail provides URL, status, HTTP code and detection method only.
- **GitHub's anonymous API is limited to 60 requests/hour.** Large GitHub-only batches fall back to HTML (fewer profile fields) or report `waf` for the tail.
- **Catalog size is 428 sites.** This is a footprint tool, not a 3,000-site Maigret replacement.
- **Decoy verification trades recall for precision.** A site that claims every handle is reported as `unverified`, even if a real profile exists there.

# Actor input Schema

## `usernames` (type: `array`):

Handles to look up. A leading @ is ignored. Use {?} to expand to \_, - and . (e.g. john{?}doe).

## `username` (type: `string`):

Optional single handle merged into `usernames`. The `usernames` array is still required.

## `sites` (type: `array`):

Only check these sites. Accepts names (GitHub), domains (github.com) or profile URLs. Leave empty for every supported site.

## `categories` (type: `array`):

Only check sites in these categories. Combined with `sites` as a union. Leave empty for all categories.

## `includeNsfw` (type: `boolean`):

Also check adult sites. Off by default.

## `includeAvailable` (type: `boolean`):

Also emit rows for sites where the handle is free, blocked, unknown or illegal. Useful for availability checks.

## `verifyClaims` (type: `boolean`):

Re-check every generic claimed site with a random decoy handle. Sites that claim any handle are demoted to unverified and kept out of `links`.

## `enrichProfiles` (type: `boolean`):

Add display name, bio, avatar, follower counts, join date, location, website and verified flag where the site exposes them.

## `outputMode` (type: `string`):

How to shape dataset rows.

## `maxUsernames` (type: `integer`):

Cap the number of handles after wildcard expansion (1–100).

## `maxSitesPerUsername` (type: `integer`):

Check only the top N priority sites per handle. 0 checks every selected site (0–500).

## Actor input object example

```json
{
  "usernames": [
    "octocat"
  ],
  "sites": [
    "GitHub"
  ],
  "includeNsfw": false,
  "includeAvailable": false,
  "verifyClaims": true,
  "enrichProfiles": true,
  "outputMode": "perUsername",
  "maxUsernames": 1,
  "maxSitesPerUsername": 0
}
```

# Actor output Schema

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

All records from the run, in the default dataset.

# 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 = {
    "usernames": [
        "octocat"
    ],
    "sites": [
        "GitHub"
    ],
    "includeNsfw": false,
    "includeAvailable": false,
    "verifyClaims": true,
    "enrichProfiles": false,
    "outputMode": "perUsername",
    "maxUsernames": 1,
    "maxSitesPerUsername": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/multi-site-username-lookup-and-profile-link-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 = {
    "usernames": ["octocat"],
    "sites": ["GitHub"],
    "includeNsfw": False,
    "includeAvailable": False,
    "verifyClaims": True,
    "enrichProfiles": False,
    "outputMode": "perUsername",
    "maxUsernames": 1,
    "maxSitesPerUsername": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/multi-site-username-lookup-and-profile-link-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 '{
  "usernames": [
    "octocat"
  ],
  "sites": [
    "GitHub"
  ],
  "includeNsfw": false,
  "includeAvailable": false,
  "verifyClaims": true,
  "enrichProfiles": false,
  "outputMode": "perUsername",
  "maxUsernames": 1,
  "maxSitesPerUsername": 0
}' |
apify call vexaloom-labs/multi-site-username-lookup-and-profile-link-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/multi-site-username-lookup-and-profile-link-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/UVRRxRDftS1uibtwC/builds/CfAicgPeuLIJQCQhA/openapi.json
