DEV Community

Cover image for Build a Cross-Platform Profile Matcher: Find the Same Creator Everywhere
Olamide Olaniyan
Olamide Olaniyan

Posted on

Build a Cross-Platform Profile Matcher: Find the Same Creator Everywhere

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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,
  };
}
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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,
  };
}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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? 👇

webdev #api #nodejs #socialmedia #javascript

Top comments (0)