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.


GET/v1/satellite/sounds/:platform/:music_id

Start Sound Lookup

Queue a sound lookup job. Returns immediately with a job_id. Poll the status route for results.

Cost per request:

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 ignores max_videos.
  • All polling and re-reads via /v1/satellite/runs/:run_id are free.

Path parameters

  • Name
    platform
    Type
    string
    Required
    *
    Description

    Only tiktok is supported today.

  • Name
    music_id
    Type
    string
    Required
    *
    Description

    TikTok music/clip ID. This is the external_id field 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's id (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 usable music_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 the trends block. Adds the $0.50 surcharge. When true, max_videos is ignored. Default false.

  • Name
    max_videos
    Type
    integer
    Description

    Number of videos to fetch when trend_analysis is false (1–100). Default 50. Ignored when trend_analysis=true.

Request

GET
/v1/satellite/sounds/:platform/:music_id
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"
  }
}

GET/v1/satellite/sounds/status/:job_id

Poll Sound Status

Poll for results. Returns processing while the job is running. When completed, returns the full result envelope described below.

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 }. note is one of insufficient_corpus, small_corpus_workable, healthy_corpus, deep_corpus. Trend analysis is skipped on insufficient_corpus.

  • Name
    trends
    Type
    SoundTrends
    Description

    Always present. When trend_analysis=false it has analyzed: false and status: "skipped". When true, 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

GET
/v1/satellite/sounds/status/:job_id
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, and engagement_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, and is_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_views accrued on this sound, deduplicated by unique_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.


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=true was 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

    true iff 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_views against the prior window's avg_views, with a ±15% deadband (anything inside the band is similar). Null when there's only one time window.


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/empty music_id.

  • Name
    401 Unauthorized
    Description

    Missing or invalid API key.

  • Name
    402 Payment Required
    Description

    Insufficient prepaid balance. $0.50 (or $1.00 with trend_analysis=true) must be available.

  • Name
    404 Not Found
    Description

    Job ID not found or expired (24h Redis TTL). Use the run_id to re-read for free.


Async workflow & timing

Status flow

processing → completed
           → failed

Timing expectations

ConfigurationTypical Duration
Default (max_videos=50)20-40 seconds
trend_analysis=true (~300)45-90 seconds
Under high trafficUp to several minutes

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_cap will be true if you ran into it.
  • "Original sounds" (creator-uploaded audio) are allowed on this endpoint, even though the platform-wide /v1/sounds index 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 + momentum triple, 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.status will be insufficient_corpus and the $0.50 surcharge is not billed.

Was this page helpful?