A brand wants to work with @sarah_creates on TikTok. She has 150K followers. Great.
But does she have an Instagram? A YouTube? A Twitter? What's her total reach?
You could ask her. Or you could search manually. Open 5 tabs. Try different username variations. Scroll through results. Hope you got the right person.
Or you could build a tool that does it in seconds. Search every platform, match by username similarity + bio + profile picture + display name, and output a unified cross-platform profile.
Here's the full build.
The Stack
- Node.js – runtime
- SociaVault API – search users and fetch profiles across platforms
- String similarity – fuzzy matching for username variations
Why Cross-Platform Matters
| Creator | TikTok Only | All Platforms |
|---|---|---|
| Followers | 150,000 | 312,400 |
| Avg Engagement | 5.8% | 4.2% (weighted) |
| Content Types | Short video only | Video + Photo + Long-form |
| CPM Potential | $150 | $312 |
Knowing the full picture changes the deal. And it gives brands confidence they're investing in someone with a real, diversified presence — not a one-platform wonder.
Step 1: Search Across Platforms
const axios = require('axios');
const API_BASE = 'https://api.sociavault.com/v1';
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const api = axios.create({
baseURL: API_BASE,
headers: { 'x-api-key': API_KEY },
});
const PLATFORMS = ['tiktok', 'instagram', 'youtube', 'twitter', 'threads', 'linkedin'];
async function searchAllPlatforms(query, limit = 10) {
const results = {};
for (const platform of PLATFORMS) {
try {
const { data } = await api.get(`/${platform}/search/users`, {
params: { keyword: query, limit },
});
results[platform] = data.users || [];
} catch (err) {
console.error(`Search failed on ${platform}: ${err.message}`);
results[platform] = [];
}
}
return results;
}
async function getProfile(platform, username) {
const { data } = await api.get(`/${platform}/profile/${username}`);
return data;
}
Step 2: Generate Username Variations
Creators rarely use the exact same handle everywhere. Generate smart variations.
function generateUsernameVariations(username) {
const clean = username.replace(/^@/, '').toLowerCase();
const variations = new Set([clean]);
// Remove common separators
variations.add(clean.replace(/[._-]/g, ''));
// Swap separators
variations.add(clean.replace(/[._-]/g, '_'));
variations.add(clean.replace(/[._-]/g, '.'));
variations.add(clean.replace(/[._-]/g, ''));
// Remove trailing numbers
variations.add(clean.replace(/\d+$/, ''));
// Remove common suffixes
const suffixes = ['official', 'real', 'tv', 'hq', 'yt', 'ig'];
for (const suffix of suffixes) {
if (clean.endsWith(suffix)) {
variations.add(clean.slice(0, -suffix.length));
}
variations.add(clean + suffix);
}
// Remove common prefixes
const prefixes = ['the', 'real', 'official', 'its', 'im'];
for (const prefix of prefixes) {
if (clean.startsWith(prefix)) {
variations.add(clean.slice(prefix.length));
}
}
return [...variations].filter(v => v.length >= 3);
}
Example: sarah_creates generates:
sarah_creates, sarahcreates, sarah.creates, sarah_createstv, sarah_createsofficial, etc.
Step 3: Score Profile Matches
Not every "sarahcreates" is the same person. Score match confidence using multiple signals.
function stringSimilarity(a, b) {
const longer = a.length > b.length ? a : b;
const shorter = a.length > b.length ? b : a;
if (longer.length === 0) return 1.0;
// Levenshtein distance
const matrix = Array.from({ length: shorter.length + 1 }, (_, i) =>
Array.from({ length: longer.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
);
for (let i = 1; i <= shorter.length; i++) {
for (let j = 1; j <= longer.length; j++) {
const cost = shorter[i - 1] === longer[j - 1] ? 0 : 1;
matrix[i][j] = Math.min(
matrix[i - 1][j] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j - 1] + cost
);
}
}
return (longer.length - matrix[shorter.length][longer.length]) / longer.length;
}
function scoreMatch(sourceProfile, candidateProfile) {
let score = 0;
let signals = [];
// Username similarity (0-30 points)
const usernameSim = stringSimilarity(
sourceProfile.username.toLowerCase().replace(/[._-]/g, ''),
candidateProfile.username.toLowerCase().replace(/[._-]/g, '')
);
const usernameScore = Math.round(usernameSim * 30);
score += usernameScore;
if (usernameScore > 20) signals.push(`username match (${Math.round(usernameSim * 100)}%)`);
// Display name similarity (0-25 points)
const sourceName = (sourceProfile.fullName || sourceProfile.displayName || '').toLowerCase();
const candidateName = (candidateProfile.fullName || candidateProfile.displayName || '').toLowerCase();
if (sourceName && candidateName) {
const nameSim = stringSimilarity(sourceName, candidateName);
const nameScore = Math.round(nameSim * 25);
score += nameScore;
if (nameScore > 15) signals.push(`display name match (${Math.round(nameSim * 100)}%)`);
}
// Bio keyword overlap (0-20 points)
const sourceBioWords = new Set((sourceProfile.biography || '').toLowerCase().split(/\s+/).filter(w => w.length > 3));
const candidateBioWords = new Set((candidateProfile.biography || '').toLowerCase().split(/\s+/).filter(w => w.length > 3));
let bioOverlap = 0;
for (const word of sourceBioWords) {
if (candidateBioWords.has(word)) bioOverlap++;
}
const bioScore = Math.min(20, Math.round((bioOverlap / Math.max(sourceBioWords.size, 1)) * 20));
score += bioScore;
if (bioScore > 10) signals.push(`bio overlap (${bioOverlap} keywords)`);
// Verified status match (0-10 points)
if (sourceProfile.isVerified && candidateProfile.isVerified) {
score += 10;
signals.push('both verified');
}
// Follower count sanity check (0-15 points)
// Same person should have roughly correlated follower counts
const ratio = Math.min(sourceProfile.followerCount, candidateProfile.followerCount) /
Math.max(sourceProfile.followerCount, candidateProfile.followerCount);
if (ratio > 0.05) { // At least 5% ratio — not a nobody vs. mega-star mismatch
score += Math.round(ratio * 15);
if (ratio > 0.2) signals.push(`follower ratio plausible (${Math.round(ratio * 100)}%)`);
}
return {
score: Math.min(100, score),
confidence: score >= 70 ? 'high' : score >= 45 ? 'medium' : 'low',
signals,
};
}
Step 4: Find Matches
async function findCrossPlatformProfiles(platform, username) {
// Get source profile
const sourceProfile = await getProfile(platform, username);
console.log(`\nSource: @${username} on ${platform} (${sourceProfile.followerCount.toLocaleString()} followers)`);
// Generate username variations to search
const variations = generateUsernameVariations(username);
console.log(`Searching ${variations.length} variations across ${PLATFORMS.length} platforms...`);
// Search other platforms
const candidates = {};
for (const targetPlatform of PLATFORMS) {
if (targetPlatform === platform) continue; // Skip source platform
candidates[targetPlatform] = [];
for (const variation of variations.slice(0, 5)) { // Limit to top 5 variations
try {
const { data } = await api.get(`/${targetPlatform}/search/users`, {
params: { keyword: variation, limit: 5 },
});
for (const user of (data.users || [])) {
// Fetch full profile for scoring
try {
const profile = await getProfile(targetPlatform, user.username);
const match = scoreMatch(sourceProfile, profile);
if (match.score >= 30) { // Minimum threshold
candidates[targetPlatform].push({
platform: targetPlatform,
username: profile.username,
displayName: profile.fullName || profile.displayName,
followers: profile.followerCount,
bio: (profile.biography || '').slice(0, 100),
...match,
});
}
} catch (e) {
// Profile fetch failed, skip
}
}
} catch (e) {
// Search failed, skip
}
}
// Deduplicate and keep best score per username
const seen = new Map();
for (const c of candidates[targetPlatform]) {
if (!seen.has(c.username) || seen.get(c.username).score < c.score) {
seen.set(c.username, c);
}
}
candidates[targetPlatform] = [...seen.values()].sort((a, b) => b.score - a.score);
}
return { sourceProfile, candidates };
}
Step 5: Generate Unified Profile
function buildUnifiedProfile(sourceProfile, sourcePlatform, matches) {
const profiles = [{
platform: sourcePlatform,
username: sourceProfile.username,
followers: sourceProfile.followerCount,
confidence: 'source',
}];
for (const [platform, candidates] of Object.entries(matches)) {
if (candidates.length > 0 && candidates[0].confidence !== 'low') {
profiles.push({
platform,
username: candidates[0].username,
followers: candidates[0].followers,
confidence: candidates[0].confidence,
matchScore: candidates[0].score,
});
}
}
const totalFollowers = profiles.reduce((sum, p) => sum + p.followers, 0);
return {
primaryUsername: sourceProfile.username,
displayName: sourceProfile.fullName || sourceProfile.displayName,
totalFollowers,
platformCount: profiles.length,
profiles,
};
}
Step 6: Print the Report
async function matchAndReport(platform, username) {
const { sourceProfile, candidates } = await findCrossPlatformProfiles(platform, username);
console.log('\n═══ CROSS-PLATFORM PROFILE MATCH ═══\n');
const unified = buildUnifiedProfile(sourceProfile, platform, candidates);
console.log(`Creator: ${unified.displayName || unified.primaryUsername}`);
console.log(`Total Reach: ${unified.totalFollowers.toLocaleString()} across ${unified.platformCount} platforms\n`);
for (const p of unified.profiles) {
const conf = p.confidence === 'source' ? '(source)' :
p.confidence === 'high' ? '✅ high confidence' :
'⚠️ medium confidence';
console.log(` ${p.platform.padEnd(12)} @${p.username.padEnd(25)} ${p.followers.toLocaleString().padStart(10)} followers ${conf}`);
}
// Show uncertain matches
for (const [plat, cands] of Object.entries(candidates)) {
const lowConf = cands.filter(c => c.confidence === 'low' && c.score >= 30);
if (lowConf.length > 0) {
console.log(`\n ❓ Possible ${plat} matches (low confidence):`);
lowConf.slice(0, 3).forEach(c => {
console.log(` @${c.username} (${c.followers.toLocaleString()} followers, score: ${c.score}) — ${c.signals.join(', ')}`);
});
}
}
return unified;
}
matchAndReport('tiktok', 'sarah_creates');
Sample Output
═══ CROSS-PLATFORM PROFILE MATCH ═══
Creator: Sarah Chen
Total Reach: 312,400 across 4 platforms
tiktok @sarah_creates 148,200 followers (source)
instagram @sarah.creates 97,800 followers ✅ high confidence
youtube @SarahCreates 66,400 followers ✅ high confidence
twitter @sarahcreates_ 12,300 followers ⚠️ medium confidence
❓ Possible threads matches (low confidence):
@sarah_creates (2,100 followers, score: 38) — username match (92%), bio overlap (3 keywords)
4 platforms confirmed. 312K total reach vs. the 148K you'd see looking only at TikTok.
Read the Full Guide
This is a condensed version. The full guide includes:
- Profile picture similarity using image hashing
- Link-in-bio cross-referencing
- Batch matching for lists of 100+ creators
- Caching matched profiles in a database
Read the complete guide on SociaVault →
Building influencer analytics tools? SociaVault provides social media data APIs for TikTok, Instagram, YouTube, and 10+ platforms. Search users, fetch profiles, and build cross-platform views with one unified API.
Discussion
How do you handle cross-platform creator identification today? Manual? Platforms? Have you found any creative signals that help confirm matches? 👇
Top comments (0)