The shared profile shape and SocialScraper interface that each social vendor implements
@zooly/social-lookup is the contract for reading a public social profile. It has no vendor, no token, and no network calls. Each vendor lives in its own package and returns a SocialScraper. Callers depend on this package, then plug in whichever vendor they want.
packages/social-lookup@zooly/social-brightdata (createBrightDataSocialScraper). Onboarding research uses this. @zooly/social-apify is deprecated and nothing in the product calls it. Bright Data is cheaper: free for 5,000 profiles a month, then $1.50 per 1,000.@zooly/social-scraper is a different, older package (Scrapfly, follower counts). It does not implement this interface.
import type { SocialScraper } from "@zooly/social-lookup";
import { createBrightDataSocialScraper } from "@zooly/social-brightdata";
const scraper: SocialScraper = createBrightDataSocialScraper();
await scraper.lookupSocialProfile("INSTAGRAM", "@janekim");
await scraper.lookupSocialProfile(
"INSTAGRAM",
"https://www.instagram.com/janekim/",
);
await scraper.lookupSocialHandle("@janekim");
await scraper.lookupSocialHandle("https://www.linkedin.com/in/williamhgates");
lookupSocialProfile checks one network. lookupSocialHandle checks every network for a bare handle, or only the network in a profile URL. LinkedIn is looked up only from a linkedin.com/in/ link.
A result is found, not_found, or error. found carries one SocialProfile: name, bio, photo, follower counts, and the vendor's raw item. Tokens and HTTP clients stay on the factory in the vendor package, so a caller can swap vendors without learning that vendor's API.
Handle and profile-URL parsing lives here, so every vendor treats @janekim, janekim, and a full profile URL the same way.
| File | Role |
|---|---|
src/scraper.ts | SocialScraper |
src/types.ts | SocialProfile, SocialLookupResult, SocialPlatform |
src/handle.ts | Handle and profile-URL parsing |