Satellite - Sound Lookups (TikTok)
Sound lookups give you the same deep-dive treatment Satellite already gives creators — but applied to every video using a specific TikTok sound. You get the sound's owner, the full normalized video list, aggregated stats (views, engagement, velocity, top creators, top hashtags, duration distribution), and — when trend_analysis=true — an LLM-derived trends block with mechanically-computed time_windows, resurged, and momentum per trend.
Sound lookups are currently TikTok only. Other platforms have no equivalent upstream endpoint. Base URL: https://api.virlo.ai/v1/satellite. All parameter names and response fields use snake_case. All responses are wrapped in a { "data": { ... } } envelope.
Start Sound Lookup
Queue a sound lookup job. Returns immediately with a job_id. Poll the status route for results.
Pricing summary:
- $0.50 base — covers fetch + normalization + stats.
- + $0.50 surcharge when
trend_analysis=true. That flag also forces a deeper fetch (~300 videos) and ignoresmax_videos. - All polling and re-reads via
/v1/satellite/runs/:run_idare free.
Path parameters
- Name
platform- Type
- string
- Required
- *
- Description
Only
tiktokis supported today.
- Name
music_id- Type
- string
- Required
- *
- Description
TikTok music/clip ID. This is the
external_idfield returned by the sounds endpoints (e.g.GET /v1/orbit/:orbit_id/sounds,GET /v1/comet/:comet_id/sounds,GET /v1/sounds) — not the sound'sid(which is Virlo's internal UUID). You can also pull it from a TikTok sound URL (the/music/<id>segment). Only TikTok sounds have a usablemusic_id.
Query parameters
- Name
trend_analysis- Type
- boolean
- Description
When
true, runs LLM-based trend detection over a diversity-sampled corpus of ~300 videos and returns thetrendsblock. Adds the $0.50 surcharge. Whentrue,max_videosis ignored. Defaultfalse.
- Name
max_videos- Type
- integer
- Description
Number of videos to fetch when
trend_analysisisfalse(1–100). Default50. Ignored whentrend_analysis=true.
Request
curl -G https://api.virlo.ai/v1/satellite/sounds/tiktok/7570679470014532374 \
-H "Authorization: Bearer {token}" \
-d trend_analysis=true
Response
{
"data": {
"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing"
}
}
Poll Sound Status
Poll for results. Returns processing while the job is running. When completed, returns the full result envelope described below.
Status results are cached in Redis for 24 hours for fast repeat polling. After that window, re-read for free via GET /v1/satellite/runs/:run_id — every completed sound lookup is persisted as a durable satellite run.
Result shape (when completed)
- Name
request- Type
- object
- Description
Echo of the request parameters (
music_id,platform,trend_analysis,max_videos).
- Name
sound- Type
- SoundMetadata
- Description
Sound owner, title, duration, original/commerce flags, cover URL, and TikTok's
reported_usage_count.
- Name
data_captured_at- Type
- string
- Description
ISO 8601 timestamp when the scrape finished. Use this to know how stale the data is.
- Name
stats- Type
- SoundStats
- Description
Aggregate statistics across the sampled videos. See Stats block.
- Name
sample_quality- Type
- SoundSampleQuality
- Description
{ truncated_by_cap, pages_fetched, note }.noteis one ofinsufficient_corpus,small_corpus_workable,healthy_corpus,deep_corpus. Trend analysis is skipped oninsufficient_corpus.
- Name
trends- Type
- SoundTrends
- Description
Always present. When
trend_analysis=falseit hasanalyzed: falseandstatus: "skipped". Whentrue, it carries the LLM-derived trend list. See Trends block.
- Name
videos- Type
- SoundCustomerVideo[]
- Description
The full normalized video list. Up to ~50 by default, up to ~300 when
trend_analysis=true.
Request
curl https://api.virlo.ai/v1/satellite/sounds/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer {token}"
Response
{
"data": { "status": "processing" }
}
Stats block
Computed mechanically from the sampled videos. Not LLM-derived.
- Name
videos_analyzed- Type
- number
- Description
Number of videos actually used to compute the stats.
- Name
views- Type
- object
- Description
{ total, avg, median, max, min }— basic view-count distribution.
- Name
engagement- Type
- object
- Description
total_likes,total_comments,total_shares,total_collects,avg_*counterparts, andengagement_rate = (likes + comments + shares) / views.
- Name
velocity- Type
- object
- Description
videos_per_week(ISO-week buckets),last_4w_avg_videos_per_week,prior_4w_avg_videos_per_week, andis_accelerating(true when last 4 weeks ≥ 15% above prior 4 weeks).
- Name
verified_creator_count / verified_creator_pct- Type
- number
- Description
Count and share of unique verified creators among the sample.
- Name
commerce_music_pct- Type
- number
- Description
Share of videos using a commercially-licensed sound clip (very useful when filtering organic vs. promoted trends).
- Name
duration_distribution- Type
- object
- Description
{ under_15s, between_15_30s, between_30_60s, over_60s }— counts only.
- Name
top_creators- Type
- SoundStatsCreator[]
- Description
Top 10 creators by
total_viewsaccrued on this sound, deduplicated byunique_id.
- Name
top_hashtags- Type
- SoundStatsHashtag[]
- Description
Top 10 hashtags by
total_views, normalized to lowercase.
- Name
top_video- Type
- SoundCustomerVideo | null
- Description
Highest-view video in the sample. Null on empty corpora.
Trends block
Only meaningful when trend_analysis=true. The block is always present so polling code can branch on trends.analyzed without optional chaining.
- Name
analyzed- Type
- boolean
- Description
True iff
trend_analysis=truewas on the request.
- Name
status- Type
- 'ok' | 'insufficient_corpus' | 'skipped'
- Description
ok= trends were generated.insufficient_corpus= the sample was too small to be statistically meaningful (we don't bill the surcharge).skipped= the caller didn't ask for trends.
- Name
summary- Type
- string
- Description
Plain-language overview of what's happening on the sound. Suitable for direct rendering in dashboards.
- Name
trends- Type
- SoundTrendItem[]
- Description
Individual trend entries.
- Name
model_used- Type
- string | null
- Description
Which LLM finished the job (Gemini-first, Claude Haiku fallback).
- Name
cost_usd / tokens_used- Type
- number | null
- Description
Bookkeeping. Useful for cost dashboards on the customer side.
SoundTrendItem
- Name
name- Type
- string
- Description
Short label for the trend ("Cinematic montage edits").
- Name
description- Type
- string
- Description
1–3 sentence explanation of the creative pattern.
- Name
tactics- Type
- string[]
- Description
Concrete production cues a creator could replicate.
- Name
confidence- Type
- number
- Description
LLM-emitted 0–1 score reflecting how clean the cluster was.
- Name
video_count- Type
- number
- Description
How many videos in the sample anchor this trend.
- Name
evidence_video_ids- Type
- string[]
- Description
IDs of videos the LLM grouped under this trend. Sanitized — any hallucinated IDs are stripped before returning to the caller.
- Name
time_windows- Type
- TimeWindow[]
- Description
Mechanically computed from real publish dates of the evidence videos. Each entry:
{ start_date, end_date, video_count, total_views, total_likes, total_comments, total_shares, avg_views, description }. Skipped entirely for evidence sets spanning less than 14 days (we'd just be reporting a single point).
- Name
resurged- Type
- boolean
- Description
trueiff this trend has ≥2 disjoint time windows. A resurgence indicates the trend faded and came back, not just continuous activity.
- Name
momentum- Type
- 'stronger' | 'weaker' | 'similar' | null
- Description
Compares the latest window's
avg_viewsagainst the prior window'savg_views, with a ±15% deadband (anything inside the band issimilar). Null when there's only one time window.
No date hallucination is possible. The LLM is only asked to cluster evidence_video_ids into named "phases" — start/end dates, view aggregates, resurged, and momentum are all derived from the actual publish_date of the evidence videos in your sample. If the LLM emits an ID that wasn't in the input, it's removed before you see it.
Durable runs (re-read for free)
Every completed sound lookup is persisted as a satellite_run row owned by your team. You can re-read it for free, forever via:
GET /v1/satellite/runs/:run_id— full result envelope, just like the status poll.GET /v1/satellite/runs?type=sound_lookup&platform=tiktok— paginated list of your sound runs.GET /v1/satellite/runs/:run_id/videos?limit=50&offset=0— paginated slice of the videos array (useful when the run has ~300 entries).
The run_id is included on every completed status payload at the top level (data.run_id) and never expires, unlike the 24-hour Redis status cache. To refresh data, start a new lookup — that will cost credits again. This is the same "pay once, read forever" contract that applies to every Satellite endpoint.
See the Satellite overview for the run-read endpoints and the Webhooks reference for the satellite.lookup.completed event (with type: "sound_lookup" discriminator) that fires on completion.
Error responses
- Name
400 Bad Request- Description
Invalid platform (must be
tiktok) or missing/emptymusic_id.
- Name
401 Unauthorized- Description
Missing or invalid API key.
- Name
402 Payment Required- Description
Insufficient prepaid balance.
$0.50(or$1.00withtrend_analysis=true) must be available.
- Name
404 Not Found- Description
Job ID not found or expired (24h Redis TTL). Use the
run_idto re-read for free.
Async workflow & timing
Status flow
processing → completed
→ failed
Timing expectations
| Configuration | Typical Duration |
|---|---|
Default (max_videos=50) | 20-40 seconds |
trend_analysis=true (~300) | 45-90 seconds |
| Under high traffic | Up to several minutes |
Always continue polling until status reaches completed or failed. Don't hardcode timeouts. Polling is free.
Recommended polling interval: Every 10-15 seconds.
Notes
- The start endpoint returns within 1–2 seconds. All fetching, normalization, stats computation, and LLM analysis happen in the background.
- Hard pagination cap: 25 pages / 500 videos. We never spin pagination indefinitely;
sample_quality.truncated_by_capwill betrueif you ran into it. - "Original sounds" (creator-uploaded audio) are allowed on this endpoint, even though the platform-wide
/v1/soundsindex filters them out. This is a deliberate carve-out — sound-satellite is the right surface for original-sound deep-dives because the same audio still drives trends. - Trends are intentionally NOT branded as "rising/peaking/fading" — the more honest framing is the explicit
time_windows[]+resurged+momentumtriple, which tells you when each pattern fired and whether it came back stronger. - Trends only fire on
sample_quality.note ≥ small_corpus_workable. If the corpus is too small,trends.statuswill beinsufficient_corpusand the $0.50 surcharge is not billed.
