SocialCrawl

Music trend detection

Detect songs gaining traction across TikTok, Instagram, and Spotify with a cross-platform heat score. 3 credits per song.

You will build an A&R trend detector that scores songs gaining traction across TikTok, Instagram, and Spotify before they reach the mainstream charts.

Cost per run: 3 credits per song (3 platforms x 1 credit). All three endpoints are standard tier, so scanning a hundred tracks costs 300 credits.

Combine three signals. GET /v1/tiktok/song counts how many videos use the sound, GET /v1/instagram/audio/reels shows whether the same audio is spreading on Reels, and GET /v1/spotify/search returns the canonical track stats. A log-weighted composite of the three catches breakouts while chart positions still lag.

By the time a song appears on a chart, the trend is priced in. The early signal lives on the creation side before it shows up on the consumption side: TikTok creation velocity spikes first, Instagram Reels tells you whether the sound crossed platforms or stayed a single-app meme, and Spotify streams confirm the move afterwards.

What you need

Three standard-tier endpoints (1 credit each), one composite score:

EndpointSignalParam
GET /v1/tiktok/songA TikTok sound and how many videos use itclipId
GET /v1/instagram/audio/reelsReels using a specific Instagram audio trackaudio_id
GET /v1/spotify/searchSpotify track / artist / podcast searchquery

Resolving the two ids

TikTok and Instagram key off ids, not track names, and both are stable per track. Resolve once, then cache them:

  • TikTok clipId: GET /v1/tiktok/song (1 credit) takes a clipId scraped from a tiktok.com/music/... URL and returns the sound's metadata, with the number of videos using it in post.engagement.views. GET /v1/tiktok/song/videos lists those videos when you want to read them.
  • Instagram audio_id: the numeric id in an instagram.com/reels/audio/{audio_id}/ URL. GET /v1/instagram/search/music (5 credits, advanced tier) searches Instagram's audio library by keyword and returns matching tracks with their ids.

Because both ids are stable, the 5-credit Instagram lookup is a one-off per track, not a per-run cost. The 3-credit figure above is the steady-state scan.

The code

TypeScript
// recipe-music-heat.ts
// Calculates a cross-platform "heat score" for one track.
// Run with: SOCIALCRAWL_KEY=sc_... npx tsx recipe-music-heat.ts

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

const BASE = "https://www.socialcrawl.dev/v1";

// Both ids are stable per track, resolve them once and store them.
const tiktokClipId = "7349589214432069122";
const instagramAudioId = "1392969992841787";
const spotifyQuery = "Espresso Sabrina Carpenter";

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;
    data?: Record<string, unknown>;
  };
}

const [tiktok, instagram, spotify] = await Promise.all([
  get("tiktok/song", { clipId: tiktokClipId }),
  get("instagram/audio/reels", { audio_id: instagramAudioId }),
  get("spotify/search", { query: spotifyQuery }),
]);

type PostItem = { post?: { engagement?: { views?: number | null } } };

// TikTok reports a total usage count for the sound.
const tiktokVideos =
  (tiktok.data?.post as PostItem["post"])?.engagement?.views ?? 0;

// Instagram returns up to about 12 reels a page. Summing views on the first
// page is a reach sample; the sound's reel count is `data.total` on the first
// page (null when the count is not available). The weight below assumes one
// page of views.
const instagramReelViews = (
  (instagram.data?.items as PostItem[] | undefined) ?? []
).reduce((sum, item) => sum + (item.post?.engagement?.views ?? 0), 0);

const spotifyPlays =
  (spotify.data?.items as PostItem[] | undefined)?.[0]?.post?.engagement
    ?.views ?? 0;

// Weighted composite. Tune the weights for your use case.
const heat =
  Math.log10(tiktokVideos + 1) * 0.5 +
  Math.log10(instagramReelViews + 1) * 0.2 +
  Math.log10(spotifyPlays + 1) * 0.3;

console.log({
  tiktok_videos: tiktokVideos,
  instagram_reel_views: instagramReelViews,
  spotify_plays: spotifyPlays,
  heat_score: heat.toFixed(2),
});

What you get back

JSON
// Aggregated, post-composite:
{
  "tiktok_videos": 2400000, // <-- `post.engagement.views` from TikTok song
  "instagram_reel_views": 41800000, // <-- summed over ONE page of audio/reels
  "spotify_plays": 1180000000, // <-- top track from Spotify search
  "heat_score": "7.44", // <-- log-weighted composite of the three above
}

What to change

  • The three ids: swap tiktokClipId, instagramAudioId, and spotifyQuery for the track you are watching. Add 5 credits once per track if you still need the /v1/instagram/search/music id lookup.
  • The weights: 0.5 / 0.2 / 0.3 favours TikTok creation velocity. Shift weight to Spotify if you care about confirmed listening rather than early creation.
  • Find candidates to score: GET /v1/instagram/music/trending (5 credits) and GET /v1/instagram/reels/trending (5 credits) surface what is already moving. For one market at a time, GET /v1/prism/trend-board?country_code=US (30 credits) returns rising_sounds, the sounds used most on TikTok's local feed for that country with how many of each sound's first page of videos are from the last 3 days, next to breakout posts, rising hashtags and Google searches. Repeat calls within 15 minutes are served from cache.
  • Deepen the Instagram sample: page audio/reels with cursor so the view sum covers more than the first page, and adjust its weight accordingly.
  • Count Instagram creation too: the first page's data.total is how many reels use the sound. Store it daily next to TikTok's data.total to compare creation velocity on both platforms.
  • Track the delta, not the level: store heat_score per day. A rising score is the signal; a high static score is a song that already broke.

On this page