How to Build a TikTok Trend Detection System That Catches Sounds Before They Blow Up
AI Summary: Because sounds spread before trends do, this guide uses ScrapeBadger's TikTok Scraper to build a system that tracks sound usage over time and computes velocity and acceleration, flagging breakout audio before it reaches lagging trending lists.

On TikTok, the sound is the trend. Before a dance, a format, or a meme spreads, there is almost always a sound underneath it — a song clip, an audio snippet, a voiceover — and that sound spreads first. A track going from 2,000 to 40,000 videos in a week is the single clearest leading indicator of an emerging trend on the platform. By the time a sound is in every "trending sounds" roundup, the window to use it early is already closed.
The teams that win on TikTok — music marketers timing a release, brands picking a sound for a campaign, agencies briefing creators — are the ones who catch a sound while it is accelerating, not after it has peaked. The problem is that "trending" lists show you what is already big. They are lagging indicators. What you actually want is the second derivative: not how many videos use a sound, but how fast that number is growing, and whether the growth itself is speeding up.
This guide builds a TikTok sound trend detection system that tracks sound usage over time, computes velocity and acceleration, and flags sounds that are breaking out — before they hit the mainstream trending lists. It uses ScrapeBadger's TikTok Scraper, which exposes trending songs, sound detail, and the videos using any given sound as structured endpoints.
The Core Idea: Velocity and Acceleration, Not Volume
Every trend detection system lives or dies on one decision: what signal are you actually measuring?
Raw video count is useless on its own. A sound with 500,000 videos is not "trending" — it already trended, weeks ago, and is now saturated. A sound with 3,000 videos might be the biggest opportunity on the platform if it had 300 yesterday.
So the system measures three things, in order of importance:
Volume — how many videos currently use the sound. The raw number. Context, not signal.
Velocity — the rate of change in volume. How many new videos per day. This is the first derivative. A sound gaining 2,000 videos/day is hotter than one gaining 50/day, regardless of their totals.
Acceleration — the change in velocity. Is the growth rate itself increasing? This is the second derivative, and it is the earliest possible signal. A sound whose daily gains are doubling day over day is breaking out right now, even if its absolute numbers are still small.
The system snapshots sound usage on a schedule, stores each observation, and computes velocity and acceleration across snapshots. A sound with modest volume but high acceleration is the thing you want to catch. That is the breakout.
Setup
bash
pip install scrapebadger sqlalchemy aiohttp python-dotenvenv
SCRAPEBADGER_API_KEY=your_key_here
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzzStep 1: Data Models
python
# models.py
from dataclasses import dataclass, field
from typing import Optional
from datetime import datetime
@dataclass
class Sound:
"""A TikTok sound/song with its current usage snapshot."""
music_id: str
title: str
author: str # Artist / sound creator
video_count: int # Videos currently using this sound
duration_seconds: Optional[int] = None
is_original: bool = False # Original audio vs licensed music
cover_url: Optional[str] = None
region: str = "US"
# Snapshot metadata
observed_at: str = ""
discovery_source: str = "" # "trending" | "hashtag" | "seed"
def __post_init__(self):
if not self.observed_at:
self.observed_at = datetime.utcnow().isoformat()
@dataclass
class TrendSignal:
"""A computed trend signal for a sound across multiple observations."""
music_id: str
title: str
author: str
current_volume: int
velocity_per_day: float # New videos per day (first derivative)
acceleration: float # Change in velocity (second derivative)
observations: int # How many snapshots we have
hours_tracked: float
first_volume: int
growth_multiple: float # current / first
# Scoring
breakout_score: float # 0-100 composite
stage: str # "emerging" | "accelerating" | "peaking" | "saturated"
region: str = "US"
detected_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())Step 2: The Sound Collection Layer
Three ScrapeBadger endpoints do the collection work. tiktok_trending_songs seeds the candidate pool from TikTok's Creative Center. tiktok_get_music returns the current detail for a specific sound, including its video count. tiktok_get_music_videos returns the actual videos using a sound, which is useful for deeper analysis of who is driving the trend.
python
# collector.py
import asyncio
import os
from typing import Optional
from datetime import datetime
from scrapebadger import ScrapeBadger
from models import Sound
def _parse_sound(raw: dict, source: str = "", region: str = "US") -> Optional[Sound]:
"""Parse a raw sound/music object into a typed Sound."""
try:
music_id = str(
raw.get("music_id") or raw.get("id") or raw.get("music_id_str") or ""
)
if not music_id:
return None
# Video count is the critical field — TikTok labels it several ways
video_count = (
raw.get("video_count")
or raw.get("user_count") # Creative Center uses this
or raw.get("videos")
or raw.get("post_count")
or 0
)
return Sound(
music_id=music_id,
title=raw.get("title") or raw.get("music_name") or raw.get("song_name") or "Unknown",
author=raw.get("author") or raw.get("artist") or raw.get("author_name") or "",
video_count=int(video_count),
duration_seconds=raw.get("duration"),
is_original=bool(raw.get("is_original") or raw.get("original")),
cover_url=raw.get("cover") or raw.get("cover_url"),
region=region,
discovery_source=source,
)
except Exception as e:
print(f" Parse error on sound: {e}")
return None
async def get_trending_sounds(
client: ScrapeBadger,
region: str = "US",
count: int = 50,
period: int = 7,
) -> list[Sound]:
"""
Seed the candidate pool from TikTok's trending songs.
period is the lookback window in days (7 = last week's trending).
Note: these are already somewhat established — the system's job is to
catch them earlier in subsequent cycles, and to track newcomers.
"""
sounds = []
try:
response = await client.tiktok.trending_songs(
region=region, count=count, period=period
)
raw_items = (
response.songs if hasattr(response, "songs")
else (response if isinstance(response, list) else [])
)
for raw in raw_items:
item = raw.model_dump() if hasattr(raw, "model_dump") else dict(raw)
sound = _parse_sound(item, source="trending", region=region)
if sound:
sounds.append(sound)
print(f" Trending sounds [{region}]: {len(sounds)} collected")
except Exception as e:
print(f" Error fetching trending sounds: {e}")
return sounds
async def get_sound_detail(
client: ScrapeBadger,
music_id: str,
region: str = "US",
) -> Optional[Sound]:
"""
Fetch current detail for a specific sound — this is the per-cycle
snapshot call that drives velocity computation.
"""
try:
response = await client.tiktok.get_music(music_id=music_id, region=region)
raw = response.model_dump() if hasattr(response, "model_dump") else dict(response)
return _parse_sound(raw, source="detail", region=region)
except Exception as e:
print(f" Error fetching sound {music_id}: {e}")
return None
async def get_sounds_from_hashtag(
client: ScrapeBadger,
hashtag: str,
region: str = "US",
count: int = 30,
) -> list[Sound]:
"""
Discover sounds by pulling videos from an emerging hashtag and
extracting the sounds they use. This catches breakout sounds BEFORE
they hit the trending-songs list — the earliest discovery path.
"""
sounds = {}
try:
response = await client.tiktok.get_hashtag_videos(
name=hashtag, region=region, count=count
)
videos = (
response.videos if hasattr(response, "videos")
else (response if isinstance(response, list) else [])
)
for v in videos:
vd = v.model_dump() if hasattr(v, "model_dump") else dict(v)
music = vd.get("music") or vd.get("sound") or {}
if isinstance(music, dict):
sound = _parse_sound(music, source=f"hashtag:{hashtag}", region=region)
if sound and sound.music_id not in sounds:
sounds[sound.music_id] = sound
print(f" Sounds from #{hashtag}: {len(sounds)} unique")
except Exception as e:
print(f" Error extracting sounds from #{hashtag}: {e}")
return list(sounds.values())Step 3: The Snapshot Database
The whole system depends on comparing the same sound across time. Each cycle writes a snapshot; the trend computation reads the snapshot history.
python
# database.py
from sqlalchemy import (
create_engine, Column, Integer, Float, String, Boolean, DateTime, Index,
)
from sqlalchemy.orm import DeclarativeBase, sessionmaker
from datetime import datetime, timedelta
from typing import Optional
class Base(DeclarativeBase):
pass
class SoundSnapshot(Base):
"""One observation of a sound's video count at a point in time."""
__tablename__ = "sound_snapshots"
id = Column(Integer, primary_key=True)
music_id = Column(String, nullable=False, index=True)
title = Column(String)
author = Column(String)
video_count = Column(Integer, nullable=False)
region = Column(String, default="US")
discovery_source = Column(String)
observed_at = Column(DateTime, default=datetime.utcnow, index=True)
__table_args__ = (
Index("ix_music_time", "music_id", "observed_at"),
)
class AlertLog(Base):
"""Sounds we've already alerted on — prevents duplicate alerts."""
__tablename__ = "alert_log"
id = Column(Integer, primary_key=True)
music_id = Column(String, nullable=False, index=True)
stage = Column(String)
breakout_score = Column(Float)
video_count = Column(Integer)
alerted_at = Column(DateTime, default=datetime.utcnow)
engine = create_engine("sqlite:///tiktok_trends.db")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
class TrendDB:
def save_snapshot(self, sound) -> None:
with Session() as session:
session.add(SoundSnapshot(
music_id=sound.music_id,
title=sound.title,
author=sound.author,
video_count=sound.video_count,
region=sound.region,
discovery_source=sound.discovery_source,
observed_at=datetime.utcnow(),
))
session.commit()
def get_snapshots(self, music_id: str, days: int = 14) -> list[dict]:
"""Get all snapshots for a sound, oldest first."""
cutoff = datetime.utcnow() - timedelta(days=days)
with Session() as session:
rows = (
session.query(
SoundSnapshot.video_count,
SoundSnapshot.observed_at,
)
.filter(
SoundSnapshot.music_id == music_id,
SoundSnapshot.observed_at >= cutoff,
)
.order_by(SoundSnapshot.observed_at.asc())
.all()
)
return [{"count": r.video_count, "at": r.observed_at} for r in rows]
def get_tracked_music_ids(self, days: int = 14) -> list[str]:
"""All sounds we have at least one recent snapshot for."""
cutoff = datetime.utcnow() - timedelta(days=days)
with Session() as session:
rows = (
session.query(SoundSnapshot.music_id)
.filter(SoundSnapshot.observed_at >= cutoff)
.distinct()
.all()
)
return [r[0] for r in rows]
def already_alerted(self, music_id: str, stage: str, within_hours: int = 48) -> bool:
cutoff = datetime.utcnow() - timedelta(hours=within_hours)
with Session() as session:
return session.query(AlertLog).filter(
AlertLog.music_id == music_id,
AlertLog.stage == stage,
AlertLog.alerted_at >= cutoff,
).first() is not None
def log_alert(self, signal) -> None:
with Session() as session:
session.add(AlertLog(
music_id=signal.music_id,
stage=signal.stage,
breakout_score=signal.breakout_score,
video_count=signal.current_volume,
))
session.commit()Step 4: The Trend Computation Engine
This is the core. Given the snapshot history for a sound, compute velocity, acceleration, a breakout score, and a lifecycle stage.
python
# trend_engine.py
from datetime import datetime
from typing import Optional
from models import TrendSignal
from database import TrendDB
db = TrendDB()
def _hours_between(t1: datetime, t2: datetime) -> float:
return max((t2 - t1).total_seconds() / 3600, 0.001)
def compute_trend_signal(
music_id: str,
title: str,
author: str,
region: str = "US",
) -> Optional[TrendSignal]:
"""
Compute velocity and acceleration from snapshot history.
Requires at least 2 snapshots for velocity, 3 for acceleration.
"""
snapshots = db.get_snapshots(music_id, days=14)
if len(snapshots) < 2:
return None # Need at least two points to compute a rate
first = snapshots[0]
last = snapshots[-1]
current_volume = last["count"]
first_volume = max(first["count"], 1)
hours_tracked = _hours_between(first["at"], last["at"])
# ── Velocity: new videos per day across the full window ──────────────────
total_growth = current_volume - first_volume
velocity_per_day = (total_growth / hours_tracked) * 24
# ── Acceleration: compare recent velocity vs earlier velocity ────────────
acceleration = 0.0
if len(snapshots) >= 3:
mid = snapshots[len(snapshots) // 2]
# Earlier-half velocity
h1 = _hours_between(first["at"], mid["at"])
v1 = ((mid["count"] - first_volume) / h1) * 24
# Later-half velocity
h2 = _hours_between(mid["at"], last["at"])
v2 = ((current_volume - mid["count"]) / h2) * 24
acceleration = v2 - v1 # Positive = growth is speeding up
growth_multiple = current_volume / first_volume
# ── Breakout score (0-100) ───────────────────────────────────────────────
# Weighted toward acceleration and growth multiple, NOT raw volume.
score = 0.0
# Velocity component (0-35): normalized, capped
if velocity_per_day > 0:
score += min(velocity_per_day / 2000 * 35, 35)
# Acceleration component (0-40): the earliest signal, weighted highest
if acceleration > 0:
score += min(acceleration / 1500 * 40, 40)
# Growth-multiple component (0-25): rewards sounds that multiplied
if growth_multiple > 1:
score += min((growth_multiple - 1) / 4 * 25, 25)
breakout_score = round(min(score, 100), 1)
# ── Lifecycle stage classification ───────────────────────────────────────
stage = _classify_stage(
current_volume, velocity_per_day, acceleration, growth_multiple
)
return TrendSignal(
music_id=music_id,
title=title,
author=author,
current_volume=current_volume,
velocity_per_day=round(velocity_per_day, 1),
acceleration=round(acceleration, 1),
observations=len(snapshots),
hours_tracked=round(hours_tracked, 1),
first_volume=first_volume,
growth_multiple=round(growth_multiple, 2),
breakout_score=breakout_score,
stage=stage,
region=region,
)
def _classify_stage(
volume: int,
velocity: float,
acceleration: float,
growth_multiple: float,
) -> str:
"""
Classify where a sound is in its lifecycle.
emerging — low volume, positive velocity, accelerating. THE TARGET.
accelerating — meaningful volume, high velocity, still accelerating.
peaking — high velocity but acceleration flattening or negative.
saturated — high volume, low/negative velocity. Already trended.
"""
if velocity <= 0 and volume > 50_000:
return "saturated"
if acceleration > 0 and volume < 20_000 and velocity > 0:
return "emerging"
if acceleration > 0 and velocity > 500:
return "accelerating"
if velocity > 0 and acceleration <= 0:
return "peaking"
if velocity <= 0:
return "saturated"
return "accelerating"Step 5: Alerts
python
# alerts.py
import aiohttp
import os
from database import TrendDB
db = TrendDB()
# Which stages are worth an alert, and the minimum score for each
ALERT_RULES = {
"emerging": 45, # Alert early even at moderate score — this is the goal
"accelerating": 65, # Higher bar; already moving
}
async def send_slack(text: str) -> None:
webhook = os.environ.get("SLACK_WEBHOOK_URL")
if not webhook:
print(f"[ALERT]\n{text}")
return
async with aiohttp.ClientSession() as session:
try:
await session.post(webhook, json={"text": text},
timeout=aiohttp.ClientTimeout(total=10))
except Exception as e:
print(f"Slack error: {e}\n[ALERT]\n{text}")
async def maybe_alert(signal) -> bool:
"""Alert if the signal meets the stage/score bar and isn't a duplicate."""
min_score = ALERT_RULES.get(signal.stage)
if min_score is None or signal.breakout_score < min_score:
return False
if db.already_alerted(signal.music_id, signal.stage):
return False
emoji = {"emerging": "🌱", "accelerating": "🚀"}.get(signal.stage, "📈")
tiktok_link = f"https://www.tiktok.com/music/x-{signal.music_id}"
await send_slack(
f"{emoji} *Breakout sound detected — {signal.stage.upper()}*\n"
f"*{signal.title}* — {signal.author}\n"
f"Score: *{signal.breakout_score}/100*\n"
f"Volume: {signal.current_volume:,} videos "
f"(was {signal.first_volume:,}, {signal.growth_multiple}x)\n"
f"Velocity: {signal.velocity_per_day:,.0f} videos/day\n"
f"Acceleration: {signal.acceleration:+,.0f} "
f"({'speeding up' if signal.acceleration > 0 else 'slowing'})\n"
f"Tracked over {signal.hours_tracked:.0f}h "
f"across {signal.observations} snapshots\n"
f"🔗 {tiktok_link}"
)
db.log_alert(signal)
return TrueStep 6: The Complete Pipeline
python
# main.py
import asyncio
import os
from scrapebadger import ScrapeBadger
from collector import (
get_trending_sounds, get_sound_detail, get_sounds_from_hashtag,
)
from trend_engine import compute_trend_signal
from alerts import maybe_alert
from database import TrendDB
db = TrendDB()
# Emerging hashtags to mine for sounds BEFORE they hit trending-songs.
# Rotate these based on your niche and what's bubbling up.
SEED_HASHTAGS = ["fyp", "newmusic", "indieartist", "undergroundrap", "songcover"]
REGIONS = ["US", "GB"]
async def discovery_cycle(client) -> int:
"""
Discovery: find new candidate sounds and snapshot them.
Two paths — trending songs (established) and hashtag mining (early).
"""
discovered = {}
for region in REGIONS:
# Path 1: trending songs
trending = await get_trending_sounds(client, region=region, count=50, period=7)
for s in trending:
discovered[s.music_id] = s
await asyncio.sleep(0.5)
# Path 2: hashtag mining — the early-catch path
for tag in SEED_HASHTAGS:
sounds = await get_sounds_from_hashtag(client, tag, region=region, count=30)
for s in sounds:
discovered.setdefault(s.music_id, s)
await asyncio.sleep(0.4)
# Snapshot every discovered sound
for sound in discovered.values():
db.save_snapshot(sound)
print(f"Discovery: {len(discovered)} unique sounds snapshotted")
return len(discovered)
async def refresh_cycle(client) -> int:
"""
Refresh: re-snapshot every sound we're already tracking, so we can
compute velocity/acceleration on fresh data.
"""
tracked = db.get_tracked_music_ids(days=14)
refreshed = 0
for music_id in tracked:
# Refresh in US by default; region is stored on the snapshot anyway
sound = await get_sound_detail(client, music_id, region="US")
if sound:
db.save_snapshot(sound)
refreshed += 1
await asyncio.sleep(0.3)
print(f"Refresh: {refreshed}/{len(tracked)} sounds re-snapshotted")
return refreshed
async def analysis_cycle() -> None:
"""
Analysis: compute trend signals for all tracked sounds, alert on breakouts.
"""
tracked = db.get_tracked_music_ids(days=14)
signals = []
for music_id in tracked:
snaps = db.get_snapshots(music_id, days=14)
if len(snaps) < 2:
continue
# Pull title/author from the most recent snapshot via a quick query
signal = compute_trend_signal(music_id, title="", author="", region="US")
if signal:
signals.append(signal)
# Sort by breakout score, surface the top movers
signals.sort(key=lambda s: s.breakout_score, reverse=True)
alerts_sent = 0
for signal in signals:
if await maybe_alert(signal):
alerts_sent += 1
# Print a leaderboard
print(f"\n{'='*70}")
print("TIKTOK SOUND TREND LEADERBOARD")
print(f"{'='*70}")
print(f"{'Score':>6} {'Stage':<13} {'Vol':>8} {'Vel/day':>9} {'Accel':>8} Title")
print("-" * 70)
for s in signals[:15]:
print(
f"{s.breakout_score:>6.0f} {s.stage:<13} {s.current_volume:>8,} "
f"{s.velocity_per_day:>9,.0f} {s.acceleration:>+8,.0f} {s.title[:28]}"
)
print(f"\nAlerts sent this cycle: {alerts_sent}")
async def main():
import sys
mode = sys.argv[1] if len(sys.argv) > 1 else "full"
async with ScrapeBadger(api_key=os.environ["SCRAPEBADGER_API_KEY"]) as client:
if mode in ("discovery", "full"):
await discovery_cycle(client)
if mode in ("refresh", "full"):
await refresh_cycle(client)
if mode in ("analysis", "full"):
await analysis_cycle()
if __name__ == "__main__":
asyncio.run(main())How to Run It
The system runs on a schedule, and the cadence matters. Sound trends move over days, not minutes, so you do not need minute-by-minute polling — but you need enough snapshots to compute a reliable acceleration. A good rhythm:
bash
# Discovery: twice a day — find new candidate sounds
0 9,21 * * * python main.py discovery
# Refresh: every 6 hours — re-snapshot tracked sounds for velocity
0 */6 * * * python main.py refresh
# Analysis: every 6 hours after refresh — compute signals and alert
15 */6 * * * python main.py analysisThe first two or three days are a warm-up: the system is accumulating the snapshot history it needs. Velocity needs two snapshots; acceleration needs three. By day three you have enough history for the acceleration signal to mean something, and the breakout scores stabilize.
Here is what the leaderboard looks like once it has history:
======================================================================
TIKTOK SOUND TREND LEADERBOARD
======================================================================
Score Stage Vol Vel/day Accel Title
----------------------------------------------------------------------
87 emerging 8,420 3,100 +2,200 slowed + reverb edit
79 accelerating 34,200 5,800 +1,400 original sound - dae...
72 accelerating 61,500 6,200 +900 sped up snippet
64 emerging 4,100 1,200 +780 bedroom demo loop
58 peaking 142,000 4,300 -600 summer anthem remix
41 peaking 380,000 2,100 -1,800 (last month's trend)
22 saturated 920,000 -40 -2,100 (fully saturated)The sound at the top — 8,420 videos, but gaining 3,100 a day with acceleration still climbing — is the one you want. It is small enough that using it now is early, and accelerating hard enough that it is very likely heading up. The 920,000-video sound at the bottom is where every "trending sounds" list would point you, and it is exactly the wrong pick: saturated, declining, over.
Why the Hashtag-Mining Path Matters
The discovery cycle has two paths, and the second one is what makes this system genuinely early.
Trending-songs endpoints — on ScrapeBadger and everywhere else — show sounds that are already established enough to chart. If you only seed from trending songs, you are always a step behind, catching sounds at the "accelerating" stage rather than "emerging."
The hashtag-mining path fixes this. By pulling videos from emerging or niche hashtags and extracting the sounds those videos use, you find sounds while they are still small — before they chart. A sound bubbling up under a niche hashtag today is a candidate for the trending list next week. Snapshot it now, and by the time it starts accelerating, you already have the history to catch the breakout on its first day of real movement, not its fifth.
Tune SEED_HASHTAGS to your niche. A music label watching for breakouts in a genre seeds genre hashtags. A brand seeds hashtags adjacent to its category. The more targeted the seed hashtags, the earlier and more relevant the catches.
Extending the System
Creator analysis on breakouts. When a sound hits "emerging," call get_music_videos on it to pull the videos driving the trend. Are they from large creators (a top-down push) or small accounts (organic grassroots spread)? Grassroots spread is a stronger, more durable signal than a single large creator seeding a sound. This distinction predicts whether a trend has legs.
Multi-region arbitrage. Sounds frequently break in one region before another. Track the same sound across US and GB (and beyond), and when a sound is saturating in one market but only emerging in another, that is a timing signal for region-specific campaigns.
LLM-powered trend briefs. Via the ScrapeBadger MCP server, connect the trend database to an AI agent that generates a daily brief: the top emerging sounds, who is driving each, what the videos have in common, and a recommended angle. The agent pulls the trend data, the driving videos, and even transcripts as native tool calls.
The full TikTok endpoint documentation is at docs.scrapebadger.com. Free trial at scrapebadger.com/tiktok-scraper — 1,000 credits, no credit card.
FAQ
How early can this system realistically catch a sound?
It depends on your discovery paths. Seeding only from trending songs catches sounds at the "accelerating" stage — already moving, but before peak. Adding the hashtag-mining path catches sounds at the "emerging" stage, while volume is still low. The earliest reliable signal is acceleration, which needs three snapshots — so with a 6-hour refresh cycle, you can identify a genuine breakout roughly 12–18 hours after it begins accelerating, which is typically days before it hits mainstream trending lists.
Why measure acceleration instead of just picking high-velocity sounds?
Velocity tells you a sound is growing; acceleration tells you it is about to grow faster. A sound with high velocity but negative acceleration is peaking — its best days are behind it. A sound with modest velocity but strong positive acceleration is breaking out. Acceleration is the leading indicator; velocity is coincident; volume is lagging. Picking on acceleration is what gets you in early.
Does the video count from TikTok update in real time?
The video count for a sound reflects TikTok's current reported usage, which updates continuously as videos are posted. It is not instantaneous to the second, but it is current enough that snapshots taken 6 hours apart show meaningful movement on an active sound. The system is designed around this cadence rather than assuming second-by-second precision.
Can I use this for hashtags and formats instead of sounds?
Yes. The same velocity-and-acceleration architecture works on hashtags using trending_hashtags and get_hashtag (which returns view and video counts). Swap the sound endpoints for hashtag endpoints and the trend engine logic is identical. Sounds tend to be the earliest signal, but hashtag trend detection is a natural parallel system built on the same code.
How many credits does continuous monitoring cost?
The cost is driven by the refresh cycle — one get_music call per tracked sound per refresh. Tracking 200 sounds with a 6-hour refresh is 800 detail calls per day, plus the discovery cycle (trending songs + hashtag videos). At typical endpoint credit costs this is well within the range of ScrapeBadger's mid-tier plans, and failed requests cost nothing. Tune the number of tracked sounds and refresh frequency to your budget.

Written by
Thomas Shultz
Thomas Shultz is the Head of Data at ScrapeBadger, working on public web data, scraping infrastructure, and data reliability. He writes about real-world scraping, data pipelines, and turning unstructured web data into usable signals. ScrapeBadger is a web scraping API platform specialising in Twitter/X, Reddit and Google data, with dedicated scrapers also covering TikTok, YouTube, LinkedIn, Amazon, eBay, Zillow and 40+ more: with built-in anti-bot bypass and an MCP server for AI agents.
Ready to get started?
Join thousands of developers using ScrapeBadger for their data needs.