Creator rosters usually have a handle per network. The hard part is deciding whether @janesmith on TikTok is the same person as @jane.smith on Instagram, then normalizing follower counts that YouTube calls subscribers and everyone else calls followers.

Call /v1/tiktok/profiles/{handle}, /v1/youtube/channel, /v1/instagram/profiles/{handle}, and /v1/twitter/profiles/{handle} with one API key. Every response shares the same envelope — branch on data.lookupStatus, merge into one warehouse row, and log meta.creditsCharged per platform. This guide uses a fictional fitness creator (sarahfit) as the running example.

You'll need an API key and the TypeScript SDK or curl. Try a live lookup in the Playground.

Why same handle ≠ same person

Platforms do not share a global user ID you can join on.

PitfallWhat goes wrongWhat to do instead
Exact-handle fan-out@nova on TikTok is a dancer; @nova on X is a game studioTreat each platform lookup as a candidate edge, not ground truth
Ignoring bio linksCRM says sarahfit everywhere; her YouTube is SarahFitOfficialParse profile.bio and channel.description for declared URLs first
HTTP 200 = dataPrivate Instagram returns 200 with lookupStatus: "private"Branch on lookupStatus, not status code
Stale CSV importsFollower count from last quarter wins the sortStore capturedAt and re-pull before client deliverables

When a platform changes its HTML, we absorb it upstream. Your parser keeps reading metrics.followers (or the YouTube equivalent inside metrics) instead of re-mapping field names every month.

DIY per platformSocial Fetch
Four proxy stacks, four breakage schedulesOne maintained API surface
Field names differ (followers vs subscriberCount)Normalized metrics + profile objects
Auth and rate-limit logic duplicatedOne x-api-key header on metered routes

The response envelope

Every profile route returns the same top-level keys:

Request
json

YouTube swaps profile for channel and may include metrics.subscribers — still inside data.metrics. Branch on lookupStatus before reading counts; not_found responses often omit metrics entirely.

Identity pipeline at a glance

Define the creator row

Decide what one person looks like in your database before the first API call. A flat table works for agencies; graph products often use a parent creators row and child creator_platforms rows.

ColumnExampleSource
creatorIdcrt_8f2ayour UUID
platformtiktokenum per network
handlesarahfitpath param you queried
lookupStatusfounddata.lookupStatus from that call
followers284000metrics.followers or subscribers
capturedAtISO timestampyour clock at ingest
requestIdreq_01…meta.requestId for support

Also reserve matchMethod (declared_link, exact_handle, fuzzy_name, manual) so analysts know why two handles were linked.

Anchor, mine bios, and fan out

1. Anchor on one network. Pick the network where your seed data is most trustworthy — usually whichever handle the client gave you first, or TikTok if you discovered the creator via search.

Request
curl -sS \
  -H "x-api-key: $SOCIALFETCH_API_KEY" \
  "https://api.socialfetch.dev/v1/tiktok/profiles/charlidamelio"

Reference: Get TikTok profile. One credit per completed lookup, including not_found.

Read the anchor response before fanning out:

Request
typescript

If the anchor is not_found, try common variants (sarah.fit, sarah_fit) before parallelizing the other networks. Each attempt is another credit.

2. Mine declared links from bios. Instagram and YouTube bios are where creators paste their "real" handles.

Request
const response = await fetch(
  "https://api.socialfetch.dev/v1/instagram/profiles/instagram",
  {
    headers: {
      "x-api-key": process.env.SOCIALFETCH_API_KEY,
    },
  }
);

const body = await response.json();

console.log(response.status, body);

Reference: Get Instagram profile.

YouTube channel lookup accepts handle, channel ID, or URL in one request:

Request
const params = new URLSearchParams({"handle":"mrbeast"});

const response = await fetch(
  `https://api.socialfetch.dev/v1/youtube/channel?${params.toString()}`,
  {
    headers: {
      "x-api-key": process.env.SOCIALFETCH_API_KEY,
    },
  }
);

const body = await response.json();

console.log(response.status, body);

Reference: Get YouTube channel.

Extract handles from bio text before fuzzy matching:

Request
typescript

Feed extractUrls output into YouTube's url query param when you see youtube.com/@… or youtu.be/… links. For Linktree-style domains, fetch the profile once, store the raw bio, and let a human confirm — do not auto-merge on a landing-page redirect alone.

X/Twitter uses the same path-parameter pattern as Instagram:

Request
bash

Reference: Get Twitter profile.

3. Fan out profile lookups. Resolve your handle list per platform (exact handle plus bio-derived candidates) in parallel. One loop handles all four because every platform returns the same top-level shape.

Example
typescript
import { SocialFetchClient } from "@socialfetch/sdk";

const client = new SocialFetchClient({
  apiKey: process.env.SOCIALFETCH_API_KEY!,
});

const handle = "mrbeast";

const [tiktok, youtube, instagram] = await Promise.all([
  client.tiktok.getProfile({ handle }),
  client.youtube.getChannel({ handle }),
  client.instagram.getProfile({ handle }),
]);

for (const result of [tiktok, youtube, instagram]) {
  if (!result.ok) {
    console.error(result.error.code, result.error.requestId);
    continue;
  }
  console.log(result.value.data.lookupStatus, result.value.meta.creditsCharged);
}

Production version with per-platform handle map and status logging:

Request
typescript

Each result carries its own meta.creditsCharged. A four-platform pass is typically four credits when all complete, even if two return not_found.

For imports of 5,000 creators, batch Promise.all in chunks of 50–100. Metered routes have no published rate cap; your balance is the limit.

Disambiguate when handles diverge

Exact-handle fan-out is step one, not the finish line:

  1. Declared link in bio — if Instagram bio contains youtube.com/@SarahFitOfficial, that edge outranks guessing sarahfit on YouTube.
  2. Display name + avatar hash — weak signal alone; useful when handles differ by one character (sarahfit vs sarah.fit).
  3. Follower magnitude — a 2M TikTok paired with a 200-subscriber YouTube is probably wrong unless the bio says "new channel."
  4. Manual queue — anything below your confidence threshold gets matchMethod: "manual" and skips auto-merge.

Store unresolved platforms as lookupStatus: "not_found" on the child row instead of copying the anchor's metrics.

Worked example — CRM says sarahfit everywhere, but Instagram bio says youtube.com/@SarahFitOfficial:

StepActionOutcome
1GET /v1/instagram/profiles/sarahfitfound, bio contains YouTube URL
2GET /v1/youtube/channel?handle=sarahfitnot_found — wrong handle
3GET /v1/youtube/channel?handle=SarahFitOfficialfound, 98k subscribers
4Merge with matchMethod: "declared_link"YouTube row linked to parent crt_8f2a

That middle not_found call still costs a credit. Parse bios before guessing identical handles.

Confidence scoring:

Request
typescript

Send low rows to a human queue. Auto-merge only high in production pipelines.

Normalize into one card

Map platform responses into one UI card without if (youtube) subscriberCount else followers scattered through React components:

Request
typescript

Pull displayName from whichever found platform you trust most — often Instagram or TikTok. YouTube's channel.title can differ ("Sarah Fit - Workouts" vs @sarahfit); show per-platform names in a detail drawer if they disagree.

Example merged JSON your dashboard API might return:

Request
json

totalReach is a business rule — sum only found platforms, or sum with a cap per network. Document the rule in code; the API will not pick for you.

Recent posts for engagement checks

Follower count gets you on a shortlist; median views tells you whether anyone watches. After profiles resolve, pull recent uploads for creators above your cutoff.

TikTok videos for the anchor handle:

Request
bash

Instagram posts paginate the same way:

Request
bash

References: TikTok profile videos · Instagram profile posts.

Compute engagement in your warehouse — the API returns per-item metrics, not a black-box score:

Request
typescript

Call GET /v1/tiktok/profiles/{handle} before interpreting an empty video list; list routes do not always expose private the same way profile routes do. See the capability matrix for route-specific lookupStatus behavior.

For spoken-content classification, pair post captions with transcript routes when hashtags are thin.

TypeScript pagination for TikTok videos:

Request
typescript

Stop after two or three pages unless you are building a forensic report — most vetting workflows only need the last 12–20 posts.

Identity resolution assumes you already have a name. Discovery fills the top of the funnel — keyword search on TikTok users when you expand a vertical.

Request
bash
Request
typescript

Reference: Search TikTok users. Persist each handle with the query that found it so you can re-run the same list after a model refresh.

Filter client-side on follower floors before spending four credits per person on full cross-platform enrichment. A search page plus 200 profile fan-outs is 201 credits — fine for a targeted import, expensive as a default loop.

TikTok video search (GET /v1/tiktok/search) is an alternate seed when you care about content niche more than account name — pull handles from high-view videos, then run the anchor-and-fan-out flow on the unique set. Deduplicate handles before enrichment; the same creator may appear in twenty search hits.

Store rows your team can audit

Flatten API responses into rows your ops team already uses (Airtable, Postgres, Google Sheets):

ColumnSourceWhy
matchMethodyour resolverExplains auto vs manual links
lookupStatusper platformDrives UI badges (private, not_found)
followersmetricsSort and filter
verifiedprofile.verifiedContract eligibility
requestIdmeta.requestIdSupport trail when a number looks wrong
creditsChargedmeta.creditsChargedClient-level cost allocation

Re-pull on a cron before QBRs or campaign reviews.

Weekly refresh job pattern (same idea as the social listening guide, but per creator row):

Request
typescript

Write meta.requestId on every upsert.

Troubleshooting

YouTube not_found but the channel loads in a browser

  • Pass the full channel URL: ?url=https://www.youtube.com/@SarahFitOfficial instead of a shortened handle.
  • Handles are case-sensitive on some legacy channels — copy the @ from the address bar.
  • @handle and /c/ChannelName URLs refer to different identifiers; use what the creator's bio declares.

Instagram found but follower count is null

  • private accounts may return identity fields without public metrics. Check lookupStatus before showing zeros.
  • Re-fetch if the profile was public five minutes ago; creators flip privacy settings often.

TikTok handle exists; fan-out returns mixed results

  • Regional or banned accounts can return not_found on one network only. Log requestId and retry once before marking dead.
  • Strip @ from path params — /v1/tiktok/profiles/@sarahfit is wrong; use sarahfit.

Merged card shows impossible total reach

  • Dedupe by person, not by handle string. Two different people named alex on TikTok and YouTube should stay two parent rows until bio links confirm a merge.
  • Do not sum private or not_found platforms into totalReach unless your product spec explicitly says to.

lookup_failed or HTTP 503

  • Not charged. Retry with exponential backoff; pass the failed meta.requestId to support if it persists.
  • A burst of 10,000 parallel profile calls is unnecessary — chunk work.

Empty video/post lists on a found profile

  • Genuinely inactive creators exist. Compare against metrics.videos or post count on the profile object.
  • Paginate — first page may be empty for sort windows; check data.page.hasMore.

Rate limits

  • No hard rate cap on metered routes; your credit balance is the practical limit. Chunk large backfills (50–100 concurrent profile calls) so retries stay manageable.

SDK Result errors vs lookupStatus

  • result.ok === false means transport or auth failed — check result.error.code and result.error.requestId.
  • result.ok === true with lookupStatus: "not_found" is a successful, billable domain outcome. Do not retry those unless the handle string changed.

Billing and credit budgeting

Each profile lookup is typically 1 credit when the call completes, including not_found and private. You are not charged for validation errors, lookup_failed, or 503 temporarily_unavailable.

Rough math for planning:

StepCalls per creatorCredits (approx.)
Anchor TikTok profile11
Bio-driven Instagram + YouTube + X fan-out33
Recent TikTok videos (1 page)11
Recent Instagram posts (1 page)11
Enriched creator row66

Import 500 creators with full enrichment: ~3,000 credits. Discovery search adds 1 credit per results page before you filter.

Log meta.creditsCharged per creatorId for client-level cost allocation. Public data only — lawful use is on you under Terms. Full matrix: Credits.

Batch import spreadsheet (500 rows, enrich everyone):

StageCalculationCredits
Profile fan-out (4 networks × 500)2,0002,000
Skip not_found anchors (~8%)−160 × 3 remaining calls−480
Video/post page for top 200 by followers200 × 2400
Net estimate~1,920

Run anchor lookup first and short-circuit dead rows — most savings come from not fanning out when TikTok returns not_found on a typoed handle.

What you can build

  • Influencer vetting — reconcile CRM handles against live follower counts and flag private accounts before outreach.
  • Cross-platform leaderboards — rank by summed reach with per-platform drill-down when totals tie.
  • Agency reporting — one dashboard template; platform tabs read the same JSON parser.
  • Creator graph products — parent/child rows with matchMethod provenance for merge audits.
  • Engagement benchmarks — median views per post across TikTok and Instagram for the same resolved person.

For a product-level overview of discovery, scoring, and refresh cadence, see the creator intelligence use case.


Next steps: Playground · Creator intelligence use case · TikTok scraping guide · API reference · Pricing