The sound makes the video.
That's not an opinion — it's TikTok's algorithm. Videos using trending sounds get 2-5x more reach than original audio. The creators who blow up aren't necessarily the most talented. They're the ones who catch the right sound early.
By the time a sound hits your For You Page, it's too late. Peak virality has passed. You needed to use it 3 days ago.
We're building a tool that solves this. A TikTok Sound Trend Analyzer that:
- Tracks popular and rising sounds in real-time
- Identifies sounds in the "early viral" stage
- Shows which sounds perform best in your niche
- Alerts you when a new sound starts trending
The Science of TikTok Sound Virality
Every TikTok sound follows a lifecycle:
- Discovery (Day 1-2): A few creators use it, one pops off
- Early Adoption (Day 3-5): Smart creators jump on. Best engagement window.
- Mass Adoption (Day 6-14): Everyone's using it. Good engagement, getting saturated.
- Oversaturation (Day 15+): Algorithm deprioritizes. Too late.
Our tool needs to catch sounds in stage 2. That's where the magic is.
The Stack
- Node.js: Runtime
- SociaVault API: TikTok music endpoints
- SQLite: Track sounds over time
- node-cron: Automated polling
Step 1: Setup
mkdir tiktok-sound-tracker
cd tiktok-sound-tracker
npm init -y
npm install axios better-sqlite3 dotenv node-cron chalk
Create .env:
SOCIAVAULT_API_KEY=your_key_here
Step 2: Database for Tracking Sound Trends
Create db.js:
const Database = require('better-sqlite3');
const path = require('path');
const db = new Database(path.join(__dirname, 'sounds.db'));
db.exec(`
CREATE TABLE IF NOT EXISTS sounds (
clip_id TEXT PRIMARY KEY,
title TEXT,
author TEXT,
duration INTEGER,
first_seen TEXT DEFAULT (datetime('now')),
last_seen TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sound_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
clip_id TEXT,
video_count INTEGER,
rank INTEGER,
is_new_on_board BOOLEAN,
snapshot_date TEXT DEFAULT (datetime('now')),
FOREIGN KEY (clip_id) REFERENCES sounds(clip_id)
);
CREATE TABLE IF NOT EXISTS sound_videos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
clip_id TEXT,
video_id TEXT,
author TEXT,
views INTEGER DEFAULT 0,
likes INTEGER DEFAULT 0,
comments INTEGER DEFAULT 0,
shares INTEGER DEFAULT 0,
collected_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (clip_id) REFERENCES sounds(clip_id)
);
`);
module.exports = db;
Step 3: Fetch Popular Sounds
The /v1/scrape/tiktok/music/popular endpoint is our main data source. It returns the hottest sounds with ranking data:
Create tracker.js:
require('dotenv').config();
const axios = require('axios');
const db = require('./db');
const API_BASE = 'https://api.sociavault.com';
const headers = { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` };
async function getPopularSounds(options = {}) {
console.log('🎵 Fetching popular TikTok sounds...');
const allSounds = [];
const maxPages = options.pages || 3;
for (let page = 1; page <= maxPages; page++) {
const { data } = await axios.get(`${API_BASE}/v1/scrape/tiktok/music/popular`, {
params: {
page,
timePeriod: options.timePeriod || 7, // Last 7 days
rankType: options.rankType,
newOnBoard: options.newOnly ? true : undefined,
commercialMusic: options.commercial ? true : undefined,
countryCode: options.country || 'US',
},
headers
});
const sounds = data.data?.musicList || data.data || [];
allSounds.push(...sounds);
if (sounds.length === 0) break;
await new Promise(r => setTimeout(r, 500));
}
console.log(` Found ${allSounds.length} sounds`);
return allSounds;
}
async function getSoundDetails(clipId) {
const { data } = await axios.get(`${API_BASE}/v1/scrape/tiktok/music/details`, {
params: { clipId },
headers
});
return data.data;
}
async function getSoundVideos(clipId) {
const { data } = await axios.get(`${API_BASE}/v1/scrape/tiktok/music/videos`, {
params: { clipId },
headers
});
return data.data?.videos || data.data || [];
}
Step 4: Store and Track Sound Trends
function storeSoundSnapshot(sounds) {
const insertSound = db.prepare(`
INSERT INTO sounds (clip_id, title, author, duration)
VALUES (?, ?, ?, ?)
ON CONFLICT(clip_id) DO UPDATE SET
last_seen = datetime('now')
`);
const insertSnapshot = db.prepare(`
INSERT INTO sound_snapshots (clip_id, video_count, rank, is_new_on_board)
VALUES (?, ?, ?, ?)
`);
const transaction = db.transaction((sounds) => {
sounds.forEach((sound, index) => {
const clipId = sound.clipId || sound.id || sound.musicId;
const title = sound.title || sound.musicName || '';
const author = sound.author || sound.authorName || '';
const duration = sound.duration || 0;
const videoCount = sound.videoCount || sound.usageCount || 0;
const isNew = sound.isNewOnBoard || sound.newOnBoard || false;
insertSound.run(clipId, title, author, duration);
insertSnapshot.run(clipId, videoCount, index + 1, isNew ? 1 : 0);
});
});
transaction(sounds);
console.log(` Stored ${sounds.length} sound snapshots`);
}
Step 5: Detect Early Viral Sounds
This is the money feature. We compare snapshots to find sounds with accelerating growth:
function detectEarlyViral() {
// Get sounds that appeared in the last 3 days
const recentSounds = db.prepare(`
SELECT
s.clip_id,
s.title,
s.author,
s.first_seen,
COUNT(ss.id) as snapshot_count,
MIN(ss.rank) as best_rank,
MAX(ss.rank) as worst_rank
FROM sounds s
JOIN sound_snapshots ss ON s.clip_id = ss.clip_id
WHERE s.first_seen >= datetime('now', '-3 days')
GROUP BY s.clip_id
ORDER BY best_rank ASC
`).all();
const earlyViral = [];
for (const sound of recentSounds) {
// Get video count growth
const snapshots = db.prepare(`
SELECT video_count, rank, snapshot_date
FROM sound_snapshots
WHERE clip_id = ?
ORDER BY snapshot_date ASC
`).all(sound.clip_id);
if (snapshots.length < 2) continue;
const firstCount = snapshots[0].video_count;
const latestCount = snapshots[snapshots.length - 1].video_count;
const growthRate = firstCount > 0 ? ((latestCount - firstCount) / firstCount) * 100 : 0;
// Rank improvement
const firstRank = snapshots[0].rank;
const latestRank = snapshots[snapshots.length - 1].rank;
const rankImprovement = firstRank - latestRank; // Positive = climbing
// Acceleration: is growth speeding up?
let acceleration = 0;
if (snapshots.length >= 3) {
const mid = Math.floor(snapshots.length / 2);
const firstHalfGrowth = snapshots[mid].video_count - snapshots[0].video_count;
const secondHalfGrowth = latestCount - snapshots[mid].video_count;
acceleration = secondHalfGrowth - firstHalfGrowth;
}
// Score the sound
const viralScore = calculateViralScore({
growthRate,
rankImprovement,
acceleration,
currentRank: latestRank,
videoCount: latestCount,
ageHours: (Date.now() - new Date(sound.first_seen).getTime()) / 3600000,
});
earlyViral.push({
...sound,
videoCount: latestCount,
growthRate: growthRate.toFixed(1),
rankImprovement,
acceleration,
viralScore,
stage: getViralStage(viralScore, latestRank),
});
}
return earlyViral.sort((a, b) => b.viralScore - a.viralScore);
}
function calculateViralScore({ growthRate, rankImprovement, acceleration, currentRank, videoCount, ageHours }) {
let score = 0;
// Growth rate (0-30 points)
score += Math.min(growthRate / 10, 30);
// Rank improvement (0-25 points)
score += Math.min(rankImprovement * 2, 25);
// Acceleration (0-25 points) — key indicator
score += Math.min(acceleration / 100, 25);
// Current rank bonus (0-10 points)
if (currentRank <= 10) score += 10;
else if (currentRank <= 25) score += 7;
else if (currentRank <= 50) score += 4;
// Freshness bonus (0-10 points) — newer = better opportunity
if (ageHours < 24) score += 10;
else if (ageHours < 48) score += 7;
else if (ageHours < 72) score += 4;
return Math.round(Math.max(0, Math.min(100, score)));
}
function getViralStage(score, rank) {
if (score >= 70 && rank <= 20) return '🔥 EARLY VIRAL — Use NOW';
if (score >= 50) return '📈 Rising Fast — Great opportunity';
if (score >= 30) return '🌱 Growing — Watch closely';
return '👀 New — Too early to tell';
}
Step 6: Analyze Sound Performance by Niche
Not every sound works for every niche. Let's check what's actually performing:
async function analyzeSoundForNiche(clipId, niche) {
console.log(`\n🔍 Analyzing sound for "${niche}" niche...`);
// Get videos using this sound
const videos = await getSoundVideos(clipId);
if (videos.length === 0) {
console.log(' No videos found for this sound');
return null;
}
// Analyze video performance
const videoStats = videos.map(v => ({
author: v.author?.uniqueId || v.author || '',
views: v.stats?.playCount || v.playCount || v.views || 0,
likes: v.stats?.diggCount || v.diggCount || v.likes || 0,
comments: v.stats?.commentCount || v.commentCount || v.comments || 0,
shares: v.stats?.shareCount || v.shareCount || v.shares || 0,
description: v.desc || v.description || '',
}));
// Check how many are in the target niche
const nicheKeywords = getNicheKeywords(niche);
const nicheVideos = videoStats.filter(v => {
const desc = v.description.toLowerCase();
return nicheKeywords.some(kw => desc.includes(kw));
});
const totalViews = videoStats.reduce((sum, v) => sum + v.views, 0);
const avgViews = totalViews / videoStats.length;
const topVideo = videoStats.reduce((max, v) => v.views > max.views ? v : max, videoStats[0]);
return {
totalVideos: videoStats.length,
avgViews: Math.round(avgViews),
topVideoViews: topVideo.views,
nicheRelevance: nicheVideos.length / videoStats.length,
nicheVideos: nicheVideos.length,
topCreators: videoStats.slice(0, 5).map(v => ({
author: v.author,
views: v.views,
})),
};
}
function getNicheKeywords(niche) {
const keywords = {
fitness: ['gym', 'workout', 'fitness', 'gains', 'exercise', 'training', 'lift'],
beauty: ['makeup', 'skincare', 'beauty', 'grwm', 'tutorial', 'glow', 'skin'],
food: ['recipe', 'cooking', 'food', 'meal', 'chef', 'kitchen', 'eat'],
fashion: ['outfit', 'fashion', 'style', 'ootd', 'wear', 'aesthetic'],
tech: ['tech', 'coding', 'developer', 'programming', 'setup', 'gadget'],
comedy: ['funny', 'comedy', 'joke', 'skit', 'prank', 'humor'],
business: ['business', 'entrepreneur', 'money', 'side hustle', 'startup'],
education: ['learn', 'tip', 'hack', 'how to', 'tutorial', 'study'],
};
return keywords[niche.toLowerCase()] || [niche.toLowerCase()];
}
Step 7: Alert System
Get notified when a sound matches your criteria:
function getAlerts(options = {}) {
const minScore = options.minScore || 50;
const earlyViral = detectEarlyViral();
const alerts = earlyViral.filter(s => s.viralScore >= minScore);
if (alerts.length === 0) {
console.log('\n😴 No trending sounds match your criteria right now.');
return [];
}
console.log(`\n🚨 ${alerts.length} SOUND ALERTS`);
console.log('═'.repeat(60));
alerts.forEach((sound, i) => {
console.log(`\n${i + 1}. ${sound.stage}`);
console.log(` 🎵 "${sound.title}" by ${sound.author}`);
console.log(` 📊 Viral Score: ${sound.viralScore}/100`);
console.log(` 📈 Growth: ${sound.growthRate}% | Rank: #${sound.best_rank}`);
console.log(` 🎬 ${sound.videoCount?.toLocaleString()} videos using it`);
console.log(` ⏱️ First seen: ${sound.first_seen}`);
});
return alerts;
}
Step 8: Automated Polling with Cron
const cron = require('node-cron');
async function pollSounds() {
console.log(`\n[${new Date().toISOString()}] Polling sounds...`);
try {
// Get popular sounds
const sounds = await getPopularSounds({ pages: 3 });
storeSoundSnapshot(sounds);
// Also check "new on board" sounds
const newSounds = await getPopularSounds({ pages: 2, newOnly: true });
storeSoundSnapshot(newSounds);
// Check for alerts
getAlerts({ minScore: 50 });
} catch (error) {
console.error('Polling error:', error.message);
}
}
function startAutoTracker() {
console.log('🤖 Starting automated sound tracker...');
console.log(' Polling every 4 hours\n');
// Poll immediately on start
pollSounds();
// Then every 4 hours
cron.schedule('0 */4 * * *', pollSounds);
}
Step 9: The Full Dashboard
async function dashboard() {
console.log('\n🎵 TIKTOK SOUND TREND DASHBOARD');
console.log('═'.repeat(60));
console.log(`📅 ${new Date().toISOString().split('T')[0]}\n`);
// Current popular sounds
const sounds = await getPopularSounds({ pages: 2 });
storeSoundSnapshot(sounds);
console.log('📊 TOP 10 SOUNDS RIGHT NOW:');
console.log('─'.repeat(60));
sounds.slice(0, 10).forEach((sound, i) => {
const title = (sound.title || sound.musicName || 'Unknown').substring(0, 40);
const author = sound.author || sound.authorName || 'Unknown';
const videos = sound.videoCount || sound.usageCount || 0;
const isNew = sound.isNewOnBoard ? ' 🆕' : '';
console.log(` ${String(i + 1).padStart(2)}. "${title}" — ${author}${isNew}`);
console.log(` ${videos.toLocaleString()} videos`);
});
// Early viral detection
console.log('\n\n🔥 EARLY VIRAL DETECTION:');
console.log('─'.repeat(60));
const earlyViral = detectEarlyViral();
if (earlyViral.length > 0) {
earlyViral.slice(0, 5).forEach((sound, i) => {
console.log(`\n ${i + 1}. [Score: ${sound.viralScore}] ${sound.stage}`);
console.log(` "${sound.title}" — ${sound.author}`);
console.log(` Growth: ${sound.growthRate}% | Rank moved: ${sound.rankImprovement > 0 ? '+' : ''}${sound.rankImprovement} positions`);
});
} else {
console.log(' No early viral sounds detected. Run tracker for 24+ hours for data.');
}
// Stats
const totalTracked = db.prepare('SELECT COUNT(*) as count FROM sounds').get();
const totalSnapshots = db.prepare('SELECT COUNT(*) as count FROM sound_snapshots').get();
console.log(`\n\n📈 TRACKER STATS:`);
console.log(` Sounds tracked: ${totalTracked.count}`);
console.log(` Data points: ${totalSnapshots.count}`);
}
async function main() {
const command = process.argv[2];
const target = process.argv[3];
switch (command) {
case 'dashboard':
await dashboard();
break;
case 'poll':
await pollSounds();
break;
case 'alerts':
const sounds = await getPopularSounds({ pages: 3 });
storeSoundSnapshot(sounds);
getAlerts({ minScore: parseInt(target) || 50 });
break;
case 'details':
const details = await getSoundDetails(target);
console.log(JSON.stringify(details, null, 2));
break;
case 'videos':
const analysis = await analyzeSoundForNiche(target, process.argv[4] || 'general');
console.log(JSON.stringify(analysis, null, 2));
break;
case 'auto':
startAutoTracker();
break;
default:
console.log('Usage:');
console.log(' node tracker.js dashboard - Full sound dashboard');
console.log(' node tracker.js poll - Poll once and store data');
console.log(' node tracker.js alerts 60 - Show sounds scoring 60+');
console.log(' node tracker.js details <clipId> - Sound details');
console.log(' node tracker.js videos <clipId> fitness - Analyze sound for niche');
console.log(' node tracker.js auto - Start auto-tracker (every 4h)');
}
}
main().catch(console.error);
Sample Output
🎵 TIKTOK SOUND TREND DASHBOARD
════════════════════════════════════════════════════════
📅 2026-02-06
📊 TOP 10 SOUNDS RIGHT NOW:
────────────────────────────────────────────────────────
1. "original sound - creativestudio" — creativestudio 🆕
2,847,000 videos
2. "Die With A Smile" — Lady Gaga & Bruno Mars
18,400,000 videos
3. "APT." — ROSÉ & Bruno Mars
12,300,000 videos
...
🔥 EARLY VIRAL DETECTION:
────────────────────────────────────────────────────────
1. [Score: 82] 🔥 EARLY VIRAL — Use NOW
"original sound - creativestudio" — creativestudio
Growth: 340% | Rank moved: +47 positions
2. [Score: 67] 📈 Rising Fast — Great opportunity
"Sweater Weather (Slowed)" — The Neighbourhood
Growth: 180% | Rank moved: +23 positions
3. [Score: 54] 📈 Rising Fast — Great opportunity
"that one sound everyone uses" — remixer_king
Growth: 95% | Rank moved: +12 positions
The Business Case
Agencies charge $500-2,000/month for "trend reports" that are basically someone scrolling TikTok and writing a Google Doc. This tool gives you better data in real-time.
| Approach | Cost | Freshness |
|---|---|---|
| Hire a trend spotter | $2,000/mo | Daily updates, subjective |
| Use Tokboard/TrendTok | $49-99/mo | Near real-time, limited data |
| Your tracker | ~$0.10/poll cycle | Every 4 hours, objective data |
Get Started
- Get your API key at sociavault.com
- Run
node tracker.js autoto start collecting data - Check
node tracker.js dashboarddaily for opportunities - Jump on sounds scoring 60+ before everyone else does
The creators who grow fastest aren't more talented. They're just faster.
By the time a sound is on your For You Page, you're already late. Don't be late.
Top comments (0)