# Sound lookup

> Pull a fresh sample of the videos using one TikTok or Instagram sound, with stats on views, creators, hashtags, and posting weeks. Add trend analysis to see the creative formats people make with it.

Source: https://dev.virlo.ai/docs/satellite/sounds
Markdown: https://dev.virlo.ai/docs/satellite/sounds.md
Section: Deep-dive lookups

## About the Virlo API (applies to every page)

- Base URL: `https://api.virlo.ai/v1`. Every request needs the header `Authorization: Bearer YOUR_API_KEY` (keys start with `virlo_tkn_`).
- Responses are JSON inside a `data` field, except the `/v1/webhooks` endpoints, which return the object or array directly. Field names are snake_case.
- Prices are in US dollars from a prepaid balance. 1 credit = $0.01. The `X-Cost` response header on each successful response is the exact charge. Errors are free.
- Slow jobs return an ID. Check its status every 15 seconds (or whatever `retry_after_seconds` says) until `finalized` is `true`.
- All docs pages: https://dev.virlo.ai/llms.txt. Every page in one file: https://dev.virlo.ai/llms-full.txt. MCP server for AI assistants: https://dev.virlo.ai/api/mcp/mcp.

---

See who uses a TikTok or Instagram sound, which hashtags go with it, and how those videos perform. Add trend analysis to see the creative formats people make with it.

**At a glance**

- **What it does:** Pulls a fresh sample of the videos using one sound, straight from the app. Stats describe that sample, not every video ever made with the sound.
- **You send:** The platform (`tiktok` or `instagram`) and the sound's ID from its link.
- **You get back:** A job ID right away, then the sound, its videos (50 by default, about 300 with trend analysis), and stats.
- **Cost:** $0.50 per lookup, or $1.00 with trend analysis. Checking progress and reopening results are free.
- **How long:** Under 1.5 minutes, about 2 with trend analysis. Up to several minutes when busy.
- **Use it when:** You need fresh data on one sound. For videos Virlo already has, [Sound videos](https://dev.virlo.ai/docs/sounds#sound-videos) is cheaper at $0.25.

> **Note:** **TikTok and Instagram only.** YouTube doesn't list videos per sound, so the `youtube` platform, or a YouTube sound ID Virlo already knows, is rejected at no charge.
>
> Instagram shares less: share and save counts, duet and stitch flags, video country, and the sound's total uses come back as `0`, `false`, or `null`. Instagram views include Facebook plays.

---

## Start a sound lookup

**Endpoint:** `GET https://api.virlo.ai/v1/satellite/sounds/:platform/:music_id`

Starts the lookup and returns a `job_id` right away. Send it to [Check lookup status](https://dev.virlo.ai/docs/satellite/sounds#poll-sound-status) for the results.

Cost per request: $0.50

**With trend analysis:** $1.00.

**Repeats within 6 hours are free.** Asking for the same sound within 6 hours of a finished lookup returns the saved result, marked `cached: true`. Watch out:

- **Adding trend analysis** to a lookup that ran without it starts a fresh $1.00 lookup.
- **Repeating a lookup that is still running** starts a second, billed lookup. Keep checking the first `job_id`.
- **A different `max_videos`** still returns the saved result as it was.
- **After 6 hours**, a new lookup is billed and saved as [a new run](https://dev.virlo.ai/docs/satellite/sounds#durable-runs). Older runs stay.

### Path parameters

- `platform` (string, required): `tiktok` or `instagram` (lowercase).
- `music_id` (string, required): The number at the end of the sound's link, such as `7392134672372746241` in `tiktok.com/music/morning-coffee-7392134672372746241` or `instagram.com/reels/audio/<id>`. From Virlo's [sound endpoints](https://dev.virlo.ai/docs/sounds), use `external_id` or `id`. An ID Virlo hasn't seen is looked up as-is and billed, so double-check it.

### Query parameters

- `trend_analysis` (string, optional): `true` adds [trend analysis](https://dev.virlo.ai/docs/satellite/sounds#trends-block) for $0.50 more and keeps every video found (usually about 300, up to 500), ignoring `max_videos`. Only `true` or `1` turn it on; `True` or `yes` count as off. The extra $0.50 is refunded if no trends come out.
- `max_videos` (integer, optional): Videos to keep and analyze, 1 to 100 (default `50`): the first ones in the platform's order, not the most viewed. Fewer is not faster or cheaper. Ignored with trend analysis.

**cURL TikTok request:**

```bash
curl -G https://api.virlo.ai/v1/satellite/sounds/tiktok/7392134672372746241 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d trend_analysis=true
```

**cURL Instagram request:**

```bash
curl https://api.virlo.ai/v1/satellite/sounds/instagram/1813620796697751 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**JavaScript request:**

```js
const musicId = '7392134672372746241'
const params = new URLSearchParams({ trend_analysis: 'true' })

const response = await fetch(
  `https://api.virlo.ai/v1/satellite/sounds/tiktok/${musicId}?${params}`,
  { headers: { Authorization: 'Bearer YOUR_API_KEY' } },
)
const body = await response.json()
const jobId = body.data.job_id // use this to check the status
```

**Python request:**

```python
import requests

music_id = '7392134672372746241'

response = requests.get(
    f'https://api.virlo.ai/v1/satellite/sounds/tiktok/{music_id}',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    # Send the string 'true'. Python's True goes out as "True", which counts as off.
    params={'trend_analysis': 'true'},
)
job_id = response.json()['data']['job_id']  # use this to check the status
```

**Response 200 Started:**

```json
{
  "data": {
    "job_id": "b5a1c328-53d6-46fe-b33b-d2d757e465d9",
    "status": "processing"
  }
}
```

**Response 200 Cached:**

```json
{
  "data": {
    "job_id": "b5a1c328-53d6-46fe-b33b-d2d757e465d9",
    "status": "completed",
    "cached": true
  }
}
```

**Response 400:**

```json
{
  "message": "Invalid platform \"youtube\". Sound lookups support tiktok and instagram. YouTube is not supported (no public \"videos using this sound\" feed).",
  "error": "Bad Request",
  "statusCode": 400,
  "code": "validation_error"
}
```

**Response 402 With trends:**

```json
{
  "statusCode": 402,
  "message": "Insufficient balance. $1.00 required, $0.70 remaining. Add funds at https://dev.virlo.ai/dashboard/billing",
  "error": "Payment Required",
  "required_credits": 100,
  "remaining_credits": 70,
  "code": "insufficient_credits"
}
```

---

## Check lookup status

**Endpoint:** `GET https://api.virlo.ai/v1/satellite/sounds/status/:job_id`

Send the `job_id` to check progress, for free. Check again every 10 to 15 seconds (polling) until `status` is `completed` (data in `result`) or `failed` (reason in `error`). To skip polling, listen for the [`satellite.lookup.completed`](https://dev.virlo.ai/docs/webhooks#lookup-payload) webhook with `data.type` `"sound_lookup"`.

Still `processing` after 15 minutes? It has likely stalled, so start again (billed as new). Don't wait: the saved run says `failed` only after 3 hours, and isn't refunded.

### What's in the result

- `run_id` (string, optional): Same as `job_id`. [Reopen the result](https://dev.virlo.ai/docs/satellite/sounds#durable-runs) with it for free.
- `sound` (object, optional): `title`, `duration_seconds`, `cover_url`, `owner` (`handle` and `nickname`, either may be `null`), `is_original` (a creator's own audio), `is_commerce_music`, and `reported_usage_count` (TikTok's count of videos using it).
- `stats` (object, optional): See [Stats](https://dev.virlo.ai/docs/satellite/sounds#stats-block).
- `trends` (object, optional): `analyzed: false` unless you asked for [trend analysis](https://dev.virlo.ai/docs/satellite/sounds#trends-block).
- `videos` (array, optional): The videos kept, in the platform's order, not by views.
- `sample_quality` (object, optional): `pages_fetched` (Virlo reads the video list in pages), `truncated_by_cap` (`true` if Virlo stopped at 25 pages or 500 videos with more left), and `note` (corpus = the video set): `insufficient_corpus` (under 10 videos), `small_corpus_workable` (10 to 49), `healthy_corpus` (50 to 199), or `deep_corpus` (200+).
- `credits_charged` (number, optional): `50` ($0.50), or `100` ($1.00) with trend analysis. Free repeats show the original charge.
- `credits_refunded` (integer, optional): Only when the trend fee bought nothing: `50` credits paid back. A `failed` status also has it, for the whole charge.
- `request / data_captured_at` (object / string, optional): Your settings (`max_videos` shows `300` with trend analysis) and when collection finished (UTC).

**Full field reference**

Each `videos[]` item and `stats.top_video` has:

- `id / url / description` (string, optional): Platform ID, link, and caption.
- `hashtags` (string[], optional): Caption hashtags, lowercase, without `#`.
- `publish_date / duration_seconds` (string / number, optional): Post time (UTC) and length in seconds.
- `views / likes / comments / shares / collects` (number, optional): `collects` means saves.
- `is_duet / is_stitch / region` (boolean / string, optional): TikTok duet and stitch flags, and a country code such as `US`.
- `author` (object, optional): `unique_id` (handle), `nickname`, `is_verified`, and `follower_count` (currently always `null`).

**cURL request:**

```bash
curl https://api.virlo.ai/v1/satellite/sounds/status/b5a1c328-53d6-46fe-b33b-d2d757e465d9 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**JavaScript request:**

```js
async function waitForSound(jobId) {
  const giveUpAt = Date.now() + 15 * 60 * 1000 // stop after 15 minutes
  while (Date.now() < giveUpAt) {
    const res = await fetch(
      `https://api.virlo.ai/v1/satellite/sounds/status/${jobId}`,
      { headers: { Authorization: 'Bearer YOUR_API_KEY' } },
    )
    if (!res.ok) throw new Error(`Status check failed: ${res.status}`)
    const { data } = await res.json()
    if (data.status === 'completed') return data.result
    if (data.status === 'failed') throw new Error(data.error)
    await new Promise((r) => setTimeout(r, 15000)) // check again in 15 seconds
  }
  throw new Error(`Still processing after 15 minutes: check /v1/satellite/runs/${jobId}`)
}
```

**Python request:**

```python
import time
import requests

def wait_for_sound(job_id):
    url = f'https://api.virlo.ai/v1/satellite/sounds/status/{job_id}'
    headers = {'Authorization': 'Bearer YOUR_API_KEY'}
    give_up_at = time.monotonic() + 15 * 60  # stop after 15 minutes
    while time.monotonic() < give_up_at:
        res = requests.get(url, headers=headers)
        res.raise_for_status()
        data = res.json()['data']
        if data['status'] == 'completed':
            return data['result']
        if data['status'] == 'failed':
            raise RuntimeError(data['error'])
        time.sleep(15)  # check again in 15 seconds
    raise TimeoutError(f'Still processing after 15 minutes: check /v1/satellite/runs/{job_id}')
```

**Response Processing:**

```json
{
  "data": { "status": "processing" }
}
```

**Response Completed:**

```json
{
  "data": {
    "status": "completed",
    "result": {
      "run_id": "b5a1c328-53d6-46fe-b33b-d2d757e465d9",
      "request": {
        "music_id": "7392134672372746241",
        "platform": "tiktok",
        "trend_analysis": true,
        "max_videos": 300
      },
      "sound": {
        "music_id": "7392134672372746241",
        "platform": "tiktok",
        "title": "morning coffee",
        "is_original": false,
        "is_commerce_music": true,
        "duration_seconds": 74,
        "cover_url": "https://p16-sg.tiktokcdn.com/...",
        "owner": { "handle": null, "nickname": "nanaacom" },
        "reported_usage_count": 78800
      },
      "data_captured_at": "2026-09-24T17:27:18.645Z",
      "credits_charged": 100,
      "stats": {
        "videos_analyzed": 289,
        "views": { "total": 219295476, "avg": 758808, "median": 316466, "max": 11133681, "min": 29280 },
        "engagement": {
          "total_likes": 16981107, "total_comments": 96687, "total_shares": 975378, "total_collects": 2092075,
          "avg_likes": 58758, "avg_comments": 335, "avg_shares": 3375, "avg_collects": 7239,
          "engagement_rate": 0.0919
        },
        "velocity": {
          "videos_per_week": [{ "iso_week": "2026-W38", "count": 10 }, { "iso_week": "2026-W39", "count": 1 }],
          "last_4w_avg_videos_per_week": 6.5,
          "prior_4w_avg_videos_per_week": 14.5,
          "is_accelerating": false
        },
        "verified_creator_count": 27,
        "verified_creator_pct": 0.0934,
        "commerce_music_pct": 1,
        "duration_distribution": { "under_15s": 15, "between_15_30s": 27, "between_30_60s": 94, "over_60s": 153 },
        "top_creators": [
          { "unique_id": "creator_a", "nickname": "Creator A", "is_verified": false, "follower_count": null, "video_count": 9, "total_views": 18781269, "avg_views": 2086808 }
        ],
        "top_hashtags": [
          { "hashtag": "mukbang", "used_count": 11, "total_views": 18489812, "avg_views": 1680892 }
        ],
        "top_video": {
          "id": "7608361337277648158",
          "url": "https://www.tiktok.com/@creator_b/video/7608361337277648158",
          "description": "It's the small wins",
          "publish_date": "2026-02-19T00:08:03.000Z",
          "duration_seconds": 18,
          "views": 11133681, "likes": 187486, "comments": 426, "shares": 5491, "collects": 13949,
          "hashtags": [],
          "is_duet": false,
          "is_stitch": false,
          "region": "US",
          "author": { "unique_id": "creator_b", "nickname": "Creator B", "is_verified": false, "follower_count": null }
        }
      },
      "sample_quality": { "truncated_by_cap": true, "pages_fetched": 25, "note": "deep_corpus" },
      "trends": {
        "analyzed": true,
        "status": "ok",
        "summary": "The corpus of 240 videos using the \"morning coffee\" sound primarily features daily routines, food and drink creations, and product showcases.",
        "trends": [
          {
            "name": "Food & Drink Creations/Reviews",
            "description": "Videos showcasing the step-by-step process of making various coffee, matcha, or other beverages, alongside mukbangs and reviews of diverse food items.",
            "tactics": ["Detail ingredients and steps for custom drinks", "Feature aesthetic drinkware or accessories"],
            "confidence": 0.91,
            "video_count": 47,
            "evidence_video_ids": ["7660615207307332877", "7671163917615336725", "..."],
            "time_windows": [
              { "start_date": "2025-09-12", "end_date": "2026-09-17", "video_count": 47, "total_views": 67283700, "total_likes": 4268543, "total_comments": 29808, "total_shares": 306930, "avg_views": 1431568, "description": "A continuous focus on food and drink content, from coffee and matcha recipes to mukbangs and fast-food reviews." }
            ],
            "resurged": false,
            "momentum": null
          }
        ]
      },
      "videos": [
        {
          "id": "7562628856616684831",
          "url": "https://www.tiktok.com/@creator_c/video/7562628856616684831",
          "description": "Trying a science experiment #science #scienceexperiments",
          "publish_date": "2025-10-18T18:22:39.000Z",
          "duration_seconds": 35,
          "views": 7284919, "likes": 1512197, "comments": 6950, "shares": 169766, "collects": 188474,
          "hashtags": ["science", "scienceexperiments"],
          "is_duet": false,
          "is_stitch": false,
          "region": "US",
          "author": { "unique_id": "creator_c", "nickname": "Creator C", "is_verified": false, "follower_count": null }
        }
      ]
    }
  }
}
```

**Response Failed:**

```json
{
  "data": {
    "status": "failed",
    "error": "A short message explaining what went wrong"
  }
}
```

**Response 404:**

```json
{
  "message": "Sound lookup job not found or expired",
  "error": "Not Found",
  "statusCode": 404,
  "code": "not_found"
}
```

---

## Stats

Plain math over the videos in `videos[]`, with no AI.

- `videos_analyzed` (number, optional): How many videos the stats cover.
- `views` (object, optional): `total`, `avg`, `median`, `max`, and `min`.
- `engagement` (object, optional): Totals and averages for likes, comments, shares, and collects. `engagement_rate` is (likes + comments + shares + collects) / views, as a fraction (`0.0919` = 9.19%).
- `velocity` (object, optional): `videos_per_week`: sampled videos per week (`2026-W38`, weeks start Monday UTC), oldest first, for the 26 most recent weeks that had videos (empty weeks are skipped). For a big sound it shows the sample, not the sound's true pace. `last_4w_avg_videos_per_week` and `prior_4w_avg_videos_per_week` average the 4 newest listed weeks and the 4 before. `is_accelerating` is `true` if the newer average is 15%+ higher, or if there is nothing earlier to compare, so a sound whose videos all fall in 4 or fewer weeks, like a brand-new one, always reads `true`.
- `top_creators` (array, optional): Up to 10 creators by total views, with `video_count`, `total_views`, and `avg_views`.
- `top_hashtags` (array, optional): Up to 20 hashtags by total views, with `used_count`, `total_views`, and `avg_views`.
- `top_video` (object | null, optional): The most viewed video, or `null` if none were found.
- `verified_creator_count / verified_creator_pct` (number, optional): Videos (not creators) from verified accounts, as a count and a 0 to 1 share.
- `duration_distribution` (object, optional): Video counts by length band, such as `under_15s`.
- `commerce_music_pct` (number, optional): Share of videos (0 to 1) whose sound TikTok licenses for business accounts. Usually matches `sound.is_commerce_music`.

---

## Trend analysis

With `trend_analysis=true`, an AI reads up to 240 videos, favoring the most viewed and at most 6 per creator where it can. It uses captions, hashtags, lengths, dates, and stats (it doesn't watch the videos) to group them into named creative patterns, with tactics a creator could copy.

Dates, counts, and view totals come from each pattern's real videos, so the AI can't invent them. The summary, descriptions, and confidence score are the AI's own. Each trend shows when it ran, whether it came back, and whether views grew.

**Full field reference**

- `status` (string, optional): `ok`, `insufficient_corpus` (under 10 videos, no trends, trend fee refunded), or `skipped` (not requested). If the AI step fails, `status` is still `ok` with empty `summary` and `trends`, and the trend fee is refunded (`credits_refunded` on the result).
- `summary` (string, optional): A short overview in the AI's words, counting only the videos it read.
- `trends[].name / description / tactics` (string / string[], optional): Label, description, and things a creator could copy.
- `trends[].confidence` (number, optional): The AI's own score from 0 to 1.
- `trends[].video_count / evidence_video_ids` (number / string[], optional): How many videos show the pattern, and their IDs. Every ID is in `videos[]`.
- `trends[].time_windows` (array, optional): Date ranges when the pattern ran, each with `start_date`, `end_date`, `video_count`, view, like, comment, and share totals, `avg_views`, and a `description`. A sample spanning under 14 days gives one window.
- `trends[].resurged` (boolean, optional): `true` with two or more windows (it faded and came back).
- `trends[].momentum` (string | null, optional): Latest window's average views against the one before: `stronger` (15%+ higher), `weaker` (15%+ lower), or `similar`. `null` with one window.

---

## Re-read past runs

Every lookup is saved as a run under its `job_id` (called `run_id` here). Reopening is free, any time:

- `GET /v1/satellite/runs/:run_id`: the full result.
- `GET /v1/satellite/runs?type=sound_lookup&platform=tiktok`: your past sound lookups.
- `GET /v1/satellite/runs/:run_id/videos?limit=50&offset=0`: the videos, a page at a time (25 per page by default, up to 200).

**Each paid lookup of a sound gets its own `run_id`.** A newer lookup never overwrites an older run, so every result you paid for stays readable. More in [Re-read past runs](https://dev.virlo.ai/docs/satellite#durable-runs).

---

## Errors

Rejected requests cost nothing. A started lookup is billed, and refunded in full if it ends `failed`.

- `400 Bad Request`: Platform not `tiktok` or `instagram`, a YouTube sound ID Virlo recognizes, an unknown Virlo `id` (UUID), empty `music_id`, `max_videos` outside 1 to 100, or an unknown query parameter.
- `401 Unauthorized`: Key missing (`missing_api_key`), or wrong or revoked (`invalid_api_key`). See [Authentication](https://dev.virlo.ai/docs/authentication).
- `402 Payment Required`: Balance under $0.50, or under $1.00 with trend analysis. `required_credits` is in credits (100 = $1.00). Below $0.50 it asks for $0.50 (`required_credits: 50`) even for a trend lookup. Free repeats need this balance too.
- `404 Not Found`: Status check: job ID unknown or over 24 hours old. [Open the saved run](https://dev.virlo.ai/docs/satellite/sounds#durable-runs) instead.
- `429 Too Many Requests`: Daily request limit reached. See [Rate limits](https://dev.virlo.ai/docs/rate-limits).

---

More in Deep-dive lookups:

- [Creator lookup](https://dev.virlo.ai/docs/satellite.md)
- [Creator lookup add-ons](https://dev.virlo.ai/docs/satellite/creators.md)
- [Video outlier check](https://dev.virlo.ai/docs/satellite/video-outlier.md)
- [Hashtag lookup](https://dev.virlo.ai/docs/satellite/hashtags.md)
