SocialCrawl

Competitor tracking

Snapshot a competitor's follower counts, engagement rates, and latest posts across TikTok, Instagram, YouTube, and X in one parallel sweep. 8 credits per competitor.

You will build a competitive dashboard that snapshots every competitor's social footprint on a schedule: follower counts, engagement rates, and what they posted this week. Growth spikes and campaign launches then show up in your data instead of in someone else's Slack message.

Cost per run: 8 credits per competitor (4 profiles + 4 post lists, 1 credit each). Tracking 10 competitors daily is 2,400 credits per month.

How do you track a competitor's social media accounts?

Fetch the competitor's profile and latest posts from each platform's standard endpoints in one Promise.all, then store each snapshot and diff it against the previous run to detect growth and new posts. Eight calls cover the four platforms that matter for most consumer brands.

What you need

Eight standard-tier endpoints (1 credit each), all behind one key and one envelope:

PlatformProfileLatest posts
TikTokGET /v1/tiktok/profile (param: handle)GET /v1/tiktok/profile/videos (param: handle)
InstagramGET /v1/instagram/profile (param: handle)GET /v1/instagram/profile/posts (param: handle)
YouTubeGET /v1/youtube/channel (param: handle)GET /v1/youtube/channel/videos (param: handle)
XGET /v1/twitter/profile (param: handle)GET /v1/twitter/user/tweets (param: handle)

When an endpoint supports engagement_rate and the required source inputs are present, read the optional computed?.engagement_rate value. Populated values use the canonical calculation, so cross-platform comparisons need no per-platform math. Keep null when the value is unavailable.

Tracking a B2B competitor? Add GET /v1/linkedin/company (5 credits) and GET /v1/linkedin/company/posts (5 credits). They take different identifiers: company accepts the company page URL, but company/posts needs the numeric company_id, which is not in that URL. Resolve it from the company response first, then cache it. Every LinkedIn content endpoint is advanced tier or above; the platform has no 1-credit profile or post surface.

The code

TypeScript
// recipe-competitor-tracking.ts
// Snapshots one competitor across 4 platforms; diff snapshots between runs.
// Run with: SOCIALCRAWL_KEY=sc_... npx tsx recipe-competitor-tracking.ts

const KEY = process.env.SOCIALCRAWL_KEY;
if (!KEY) throw new Error("Set SOCIALCRAWL_KEY");

const BASE = "https://www.socialcrawl.dev/v1";
const competitor = "duolingo"; // same handle on all four platforms here

async function get(path: string, params: Record<string, string>) {
  const url = new URL(`${BASE}/${path}`);
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
  const res = await fetch(url, { headers: { "x-api-key": KEY! } });
  return (await res.json()) as {
    success: boolean;
    platform: string;
    data?: {
      author?: { username: string | null; followers: number | null };
      computed?: { engagement_rate: number | null };
      items?: Array<{
        post?: {
          text?: string | null;
          created_at?: string | null;
          engagement?: { views?: number | null; likes?: number | null };
        };
      }>;
    };
  };
}

// ── Step 1: 8 calls in parallel, 4 profiles + 4 post lists ───────────────
const [
  ttProfile,
  igProfile,
  ytProfile,
  twProfile,
  ttPosts,
  igPosts,
  ytPosts,
  twPosts,
] = await Promise.all([
  get("tiktok/profile", { handle: competitor }),
  get("instagram/profile", { handle: competitor }),
  get("youtube/channel", { handle: competitor }),
  get("twitter/profile", { handle: competitor }),
  get("tiktok/profile/videos", { handle: competitor }),
  get("instagram/profile/posts", { handle: competitor }),
  get("youtube/channel/videos", { handle: competitor }),
  get("twitter/user/tweets", { handle: competitor }),
]);

// ── Step 2: Build today's snapshot ─────────────────────────────────────────
const posts = [ttPosts, igPosts, ytPosts, twPosts];
const snapshot = {
  date: new Date().toISOString().slice(0, 10),
  handle: competitor,
  platforms: [ttProfile, igProfile, ytProfile, twProfile]
    .filter((p) => p.success && p.data)
    .map((p, i) => ({
      platform: p.platform,
      followers: p.data!.author?.followers ?? null,
      engagement_rate: p.data!.computed?.engagement_rate ?? null,
      posts_fetched: posts[i]?.data?.items?.length ?? 0,
      latest_post: posts[i]?.data?.items?.[0]?.post?.text?.slice(0, 80) ?? null,
    })),
};

console.table(snapshot.platforms);

// ── Step 3: Diff against your stored snapshot from the previous run ───────
// Persist `snapshot` to a DB table keyed by (handle, date), then:
//   follower_delta   = today.followers - yesterday.followers
//   new_posts        = posts with created_at > yesterday's run time
//   engagement_jump  = today.engagement_rate / trailing 7-day average
// Alert when any delta crosses your threshold.

What you get back

JSON
// console.table(snapshot.platforms):
// ┌─────────┬─────────────┬───────────┬─────────────────┬───────────────┬──────────────────────────────┐
// │ (index) │  platform   │ followers │ engagement_rate │ posts_fetched │         latest_post          │
// ├─────────┼─────────────┼───────────┼─────────────────┼───────────────┼──────────────────────────────┤
// │    0    │ "tiktok"    │  17800000 │     0.1124      │      20       │ "the owl has no chill..."    │
// │    1    │ "instagram" │   4900000 │     0.0381      │      12       │ "new feature drop 🦉"        │
// │    2    │ "youtube"   │   8120000 │     0.0512      │      30       │ "How Duo learned Spanish"    │
// │    3    │ "twitter"   │  16200000 │      null       │      100      │ "spanish or vanish"          │
// └─────────┴─────────────┴───────────┴─────────────────┴───────────────┴──────────────────────────────┘

What to change

  • competitor: handles differ per platform in reality. Keep a { tiktok, instagram, youtube, twitter } handle map per competitor instead of one string.
  • Which platforms: drop a pair to save 2 credits per run, or add the LinkedIn pair to reach 18 credits per competitor (8 + 5 for company + 5 for company/posts).
  • Where snapshots go: the script prints a table. Write it to a table keyed by (handle, date) so Step 3 has something to diff against.
  • Your alert thresholds: the three deltas in Step 3 are the ones worth watching. Pick the multiples that mean something for your category.

On this page