# TikTok Sound Scraper & Monitor - Video Count & Audio Growth (`neverempty/tiktok-sound-scraper`) Actor

Track TikTok sounds: how many videos use a sound, its title, artist, length and original-sound flag, and the top videos using it. Monitoring mode returns a sound only when its video count changes, with the growth. Unavailable sounds come back free with the reason.

- **URL**: https://apify.com/neverempty/tiktok-sound-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.92 / 1,000 sound row returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## TikTok Sound Scraper & Monitor - Video Count & Audio Growth

Track TikTok sounds from their URLs: **how many videos use a sound**, its title, author, length, album and original-sound flag, the artist shown on TikTok's sound card, and **the top videos using it** with their view counts. The video count comes with **the step TikTok rounded it to**, so you know that `1300000` is TikTok's "1.3M", not an exact number.

Turn on **monitoring mode**, schedule the Actor, and a run returns a sound **only when its video count changed** since the row last returned for it, with the change and the videos per hour. Sounds TikTok's sound card does not return come back as free rows with TikTok's own error code.

### What you can use it for

- **Trend tracking**: schedule monitoring mode on the sounds you care about and see which ones are gaining videos, and how fast (`videoCountDelta`, `videosPerHour`).
- **Music marketing and campaign reporting**: paste the sounds of a release or campaign and export video counts, top videos and their views to a spreadsheet or dashboard.
- **Creator and brand research**: see which videos TikTok shows first for a sound, and who made them.
- **Sound catalogues**: get the title, author, length, album and TikTok's original-sound flag for a list of sound ids.

### How it works

1. Paste sound URLs (`https://www.tiktok.com/music/SugarCrash-6920125567752734722`), embed URLs (`https://www.tiktok.com/embed/music/6920125567752734722`) or bare numeric sound ids.
2. For each sound the Actor opens TikTok's public **sound card** (`https://www.tiktok.com/embed/music/<id>`) and checks that TikTok answered with **the same sound id you asked for**. The card gives the video count, the artist name, the cover image and up to 10 top videos.
3. The card has no title, so the Actor opens the page of one public video from the card and reads the sound's title, author, length, album and original-sound flag from it - **only if that video's sound id is the sound you asked for** (from Apify on 2026-09-15, 19 of 19 videos opened this way carried the sound's own id). If the first video does not work it tries a second one; if neither does, the row is still returned with the counts and `soundDetailsStatus` says why the title is missing.
4. Sounds the card does not return get a free row with TikTok's error code.

TikTok's own sound API (`/api/music/detail`) needs a signed request: it answered with an empty response (HTTP 200, 0 bytes) from Apify and from a home connection alike on 2026-09-15, so this Actor does not use it.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `sounds` | example sounds | Sound URLs, embed URLs or numeric sound ids, one per line (commas also separate entries, and so do spaces between entries that are all sound URLs or ids). A repeated sound is read and charged once; the repeat gets a free `duplicate` row. Short links (`vm.tiktok.com`) are not followed. |
| `maxResults` | `100` | With monitoring off, stop after this many rows. In monitoring mode it does not cut changes. Up to 1,000 entries per run. |
| `includeSoundDetails` | `true` | Open one video page per returned sound to read the title, author, length, album and original-sound flag. Off = one request per sound and those columns stay empty. The price is the same. |
| `monitoringMode` | `false` | Remember each sound's video count and return a sound only when it changes. |
| `resetMonitoringState` | `false` | Forget every remembered count and start a fresh baseline. Turn it off again after one run. |
| `useProxy` | `true` | Retry a page through a US proxy only if TikTok answers it with a bot check. |

```json
{
    "sounds": [
        "https://www.tiktok.com/music/SugarCrash-6920125567752734722",
        "https://www.tiktok.com/music/Come-and-Get-Your-Love-6688272830112566021"
    ],
    "monitoringMode": true
}
```

If you leave `sounds` out of the input with monitoring off, the two example sounds above are read (their URLs appear in `inputSound` and the run log says so). In monitoring mode `sounds` is required, so a schedule never pays to watch the examples. An empty list is rejected with a free `invalid-input` row instead of reading the examples.

### Output

One row per sound, shortened (2 of the 10 top videos shown). The counts and details were read from Apify on 2026-09-15 (UTC); the monitoring fields show what a later run returns after the video count moves from 1.2M to 1.3M:

```json
{
    "status": "ok",
    "inputSound": "https://www.tiktok.com/music/MONTERO-6942551356402042881",
    "soundId": "6942551356402042881",
    "soundUrl": "https://www.tiktok.com/music/MONTERO-Call-Me-By-Your-Name-6942551356402042881",
    "embedUrl": "https://www.tiktok.com/embed/music/6942551356402042881",
    "title": "MONTERO (Call Me By Your Name)",
    "authorName": "Lil Nas X",
    "embedArtist": "dreamboy.",
    "isOriginal": false,
    "durationSeconds": 40,
    "album": "MONTERO (Call Me By Your Name)",
    "soundDetailsStatus": "ok",
    "soundDetailsFromVideoId": "6947415259955088646",
    "videoCount": 1300000,
    "videoCountRoundingUnit": 100000,
    "videoCountIsRounded": true,
    "videoCountAtLeast": 1200001,
    "videoCountAtMost": 1399999,
    "tiktokRegion": "US",
    "tiktokDataCenter": "US-TTP2",
    "topVideos": [
        {
            "videoId": "6947415259955088646",
            "videoUrl": "https://www.tiktok.com/@jorgechaconh/video/6947415259955088646",
            "authorUsername": "jorgechaconh",
            "description": "You know that you can #callmebyyourname 👼 @lilnasx 🖤",
            "playCount": 87800000,
            "playCountRoundingUnit": 100000,
            "isPrivate": false
        },
        {
            "videoId": "6946346066350492933",
            "videoUrl": "https://www.tiktok.com/@worms.forbrains/video/6946346066350492933",
            "authorUsername": "worms.forbrains",
            "description": "well I don’t have a pole, but I have a skateboard 😈 @lilnasx #cmbyn",
            "playCount": 31400000,
            "playCountRoundingUnit": 100000,
            "isPrivate": false
        }
    ],
    "topVideosCount": 10,
    "change": "count-changed",
    "isFirstCheck": false,
    "previousCheckedAt": "2026-09-15T13:36:00.000Z",
    "previousCountReadAt": "2026-09-14T14:36:00.000Z",
    "previousVideoCount": 1200000,
    "previousTiktokRegion": "US",
    "hoursSincePreviousCount": 24,
    "videoCountDelta": 100000,
    "videoCountDeltaAtLeast": -99998,
    "videoCountDeltaAtMost": 299998,
    "videosPerHour": 4166.67
}
```

Every row also has `source`, `scrapedAt` and `coverUrl` (the sound's cover image as TikTok links it; 7 of the 11 cover links read from Apify on 2026-09-15 carried an expiry time (`x-expires`) about two days later, so save the image rather than the link).

#### The video count is TikTok's rounded figure

TikTok's sound card shows the video count rounded. Measured on 2026-09-15 across 39 sounds read from a home connection and 11 read from Apify:

| Video count | Rounded to | Example |
| --- | --- | --- |
| below 10,000 | not rounded | `647` |
| 10,000 to 999,999 | steps of 100 | `952000` |
| 1,000,000 and above | steps of 100,000 | `1300000` |

`videoCountRoundingUnit` is that step, and `videoCountAtLeast` / `videoCountAtMost` are the lowest and highest whole numbers the shown figure could stand for, taking one step either way because the direction of TikTok's rounding has not been measured. In monitoring mode `videoCountDeltaAtLeast` / `videoCountDeltaAtMost` give the same range for the change, which is why a shown jump of `+100000` can be anything from `-99998` to `+299998`. **A sound can gain videos without its shown count changing**, so a large sound is only returned when its count crosses a rounding step.

This is the same figure TikTok's own sound page shows: for 2 of 2 sounds opened in a browser from a home connection on 2026-09-15, the page said "98.8K videos" and "3.9M videos" and the card read from the same connection right after gave `98800` and `3900000`.

Reading the same sound again from the same place gave the same figure: 13 of 13 sounds showed the same count on four reads from the same connection within about 30 minutes, and reading sounds from Apify twice in a row gave matching counts.

#### Counts depend on which TikTok region answers

TikTok answers from different servers in different countries, and they do not show the same video count. On 2026-09-15 the same 13 sounds were read at the same moment from Apify (`tiktokRegion` `US`) and from a connection in Japan (`JP`): **4 of 13 differed**, for example `952000` from Apify against `910800` from Japan, and `647` against `648`.

So every row says which region answered (`tiktokRegion`, `tiktokDataCenter`). Requests from Apify are answered by TikTok's US servers, and the proxy used after a bot check is located in the US too. In monitoring mode a count is only compared with a count from the same region: if a different region answers, the row comes back as `change: "region-changed"` without a change figure, and later runs compare against that region.

#### Top videos

`topVideos` is the list TikTok puts on the sound card: up to 10 videos, **not all videos that use the sound, and not sorted by views**. The list can change length between reads (10, 9, then 10 videos for the same sound on 2026-09-15), so it is never used to decide whether a sound changed.

`playCount` is TikTok's rounded view count. The card stores it as a 32-bit number, which overflows for videos above about 2.1 billion views: Zach King's 2.4-billion-view video is `-1894967296` on the card. Such a value is returned as `null` rather than as a wrong number, and so is any value that cannot be one of TikTok's rounded figures.

#### Title, author and the artist on the card

`title`, `authorName`, `durationSeconds`, `album` and `isOriginal` come from the video page (see How it works). `embedArtist` is the artist name printed on the sound card, which is not always the same as `authorName`: 2 of 39 sounds differed on 2026-09-15 (for "MONTERO (Call Me By Your Name)" the card says `dreamboy.` and the video page says `Lil Nas X`). Both are returned as TikTok gives them.

`isOriginal` is TikTok's own `original` flag, passed through unchanged. It does not always match the title: the sound titled "original sound - NASA" has it set to `false`.

`soundDetailsStatus` is `ok`, `not-requested` (`includeSoundDetails` is off), `no-public-video` (the card lists no public video), `different-sound` (the video page belongs to another sound), `video-unavailable`, `blocked` or `unreadable`. The row is charged either way, because the video count was read.

### Rows that are never charged

| Status | Meaning |
| --- | --- |
| `sound-not-returned` | TikTok's sound card answered HTTP 400 with its own error code (`tiktokErrorCode`). It does this for ids that do not exist (`100001`), **and also for some sounds that do exist**: 5 of 44 sounds taken from real video pages got it on 2026-09-15, with codes `10219` and `10218`, and 4 of those 5 were not original sounds (licensed or library music). So this row does not claim the sound does not exist or was removed. |
| `different-sound-returned` | TikTok answered with a different sound id than the one asked for. |
| `no-count-returned` | The card answered without a usable video count. |
| `blocked` | TikTok answered with a bot check or an empty response, even after retrying. |
| `unreadable` | The card could not be read. |
| `invalid-input` | The entry is not a sound URL or id, or it is a short link. |
| `duplicate` | The same sound appeared earlier in the list. |
| `no-change` | Monitoring mode: no sound checked had a different video count. |
| `not-checked` | The end of the list was not read because `maxResults` rows were returned or more than 1,000 entries were given. |
| `budget-reached` | The run's maximum total charge had no room for another row (or check and row). |

### Pricing

- **$4.00 per 1,000 sound rows**, charged only for rows with `status: "ok"`.
- In monitoring mode, also **$0.30 per 1,000 sound checks**: every sound whose video count was read costs a check, whether or not it changed. Example: 50 sounds checked every hour is 36,000 checks a month, which is **$10.80**, plus $4.00 per 1,000 rows for the sounds that changed.

The Actor reads only as many sounds as the run's maximum total charge can pay for (with monitoring on, a check and a change row each), and a free row says how many were not read.

### Monitoring mode

- The first run returns every sound once with `change: "first-check"`.
- Later runs return a sound with `change: "count-changed"` when the shown video count differs from the row last returned for it, or `"region-changed"` when a different TikTok region answered.
- A sound that is not returned keeps the count of the row last returned for it, so the next change adds up from there.
- Counts are remembered per sound for this Actor, not per list. **Do not put the same sound in two schedules that can run at the same time**: each run merges only the sounds it handled into the remembered counts and checks its write, but Apify's key-value store has no atomic update, so two runs finishing at the same moment can still overwrite each other.
- If Apify restarts or moves a run, it continues after the sounds it had already handled, and a sound whose row is already in the run's dataset is not read or charged again (in monitoring mode its count is remembered from that row).

### Limits

- Up to 1,000 entries per run, read one at a time with a short pause between requests. Measured from Apify on 2026-09-15: 2.31 seconds per sound for 17 sounds whose title was read, and 1.12 seconds per sound for 41 sounds where only the sound card was read, so 1,000 sounds take roughly 40 minutes with `includeSoundDetails` on. The default run timeout is 3,600 seconds; raise it for longer lists.
- The Actor does not return the full list of videos using a sound; `topVideos` is TikTok's card list of up to 10.
- It does not find trending sounds for you; give it the sounds to read or watch.

# Actor input Schema

## `sounds` (type: `array`):

Sounds to read: https://www.tiktok.com/music/SugarCrash-6920125567752734722 (with or without the scheme), https://www.tiktok.com/embed/music/6920125567752734722, or the numeric sound id. One per line (commas also separate entries, and so do spaces between entries that are all sound URLs or ids). Short links (vm.tiktok.com) are not followed: open one in a browser and paste the full sound URL. A repeated sound is read and charged once and the repeat gets a free 'duplicate' row. If you leave this field out with monitoring off, two example sounds are read; with monitoring on it is required. An empty list is rejected.

## `maxResults` (type: `integer`):

With monitoring off, reading stops once this many charged rows have been returned, and a free row says how many entries at the end of the list were not read. Sounds TikTok does not return and unreadable entries do not count. In monitoring mode it does not limit the changes returned: every sound in the list is checked, as far as the run's maximum total charge allows. Up to 1,000 entries are read per run.

## `includeSoundDetails` (type: `boolean`):

TikTok's sound card has the video count, the artist name and the top videos, but not the title. On = for each row that is returned, the Actor also opens the page of one public video on the card (a second one if the first fails) and takes the title, author name, length, album and TikTok's original-sound flag from it, but only if that video's sound id is the sound you asked for. Off = one request per sound, and those columns stay empty. It does not change the price.

## `monitoringMode` (type: `boolean`):

Off = every sound comes back with its current counts, charged per row. On = the Actor remembers each sound's video count and, on later runs, returns a sound only when the video count TikTok shows changed since the row last returned for it, with the change, the videos per hour and the range the change could be in given TikTok's rounding. The first run returns every sound once to set the baseline. **In monitoring mode every sound whose video count is read costs $0.30 per 1,000 checks, changed or not** (sounds TikTok does not return, unreadable entries and bot checks are free), plus the row price for the rows returned. Example: 50 sounds every hour = 36,000 checks a month = $10.80. The Actor reads only as many sounds as the run's maximum total charge can pay for with a check and a change row each. Counts are remembered per sound; do not put the same sound in two schedules that can run at the same time.

## `resetMonitoringState` (type: `boolean`):

Clears every remembered count for this Actor, so the next monitoring run returns each sound once again as a first check. This affects all your monitoring runs, because counts are stored per sound rather than per list. Turn it off again after one run: left on in a schedule, every run returns every sound as a first check and charges the row price for it.

## `useProxy` (type: `boolean`):

Every page is requested directly first. Only if TikTok answers a page with a bot check, an empty response or no response is that page retried through a proxy session located in the United States (TikTok shows different video counts in different countries, and requests from Apify are answered by TikTok's US servers); the next page is requested directly again. With it off, that answer is retried directly and then returned as a free 'blocked' row, and no proxy is paid for.

## Actor input object example

```json
{
  "sounds": [
    "https://www.tiktok.com/music/SugarCrash-6920125567752734722",
    "https://www.tiktok.com/music/Come-and-Get-Your-Love-6688272830112566021"
  ],
  "maxResults": 100,
  "includeSoundDetails": true,
  "monitoringMode": false,
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

## `results` (type: `string`):

One row per TikTok sound whose video count was read: the sound id and URL, the title, author name, artist on the sound card, length, album and original-sound flag, the cover image, the number of videos using the sound as TikTok shows it with its rounding step and range, the TikTok region that answered, and up to 10 top videos on the sound's card with their view counts; in monitoring mode the change in the video count since the row last returned for the sound, with the videos per hour and the range TikTok's rounding allows. Sounds TikTok does not return, bot checks and invalid input come back as free rows that say why.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sounds": [
        "https://www.tiktok.com/music/SugarCrash-6920125567752734722",
        "https://www.tiktok.com/music/Come-and-Get-Your-Love-6688272830112566021"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/tiktok-sound-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "sounds": [
        "https://www.tiktok.com/music/SugarCrash-6920125567752734722",
        "https://www.tiktok.com/music/Come-and-Get-Your-Love-6688272830112566021",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/tiktok-sound-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sounds": [
    "https://www.tiktok.com/music/SugarCrash-6920125567752734722",
    "https://www.tiktok.com/music/Come-and-Get-Your-Love-6688272830112566021"
  ]
}' |
apify call neverempty/tiktok-sound-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/tiktok-sound-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/UfOlyqTTt9Dq9jce7/builds/lFIobUKU0jfsKGTgN/openapi.json
