# TikTok Content Discovery Scraper · Hashtags & Sounds (`data-slayer/tiktok-content-discovery`) Actor

Discover public TikTok videos by exact hashtag/challenge or sound/music. Get one typed row per unique video with creator, engagement, music, hashtag, and timestamp fields, with bounded depth and clear empty or partial outcomes.

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

## Pricing

from $1.00 / 1,000 content rows

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 Content Discovery Scraper · Hashtags & Sounds

Discover public TikTok videos tied to an exact hashtag/challenge or an exact sound/music ID. Choose one discovery mode, supply exact targets, and receive one typed row per unique public video with creator, engagement, music, hashtag, and timestamp fields.

### What this Actor does

Use this Actor when you know the **exact** hashtag/challenge or sound/music you care about and want the public videos associated with it. It is intentionally different from keyword video search and from region/window trend research:

- **Hashtag mode** resolves an exact hashtag or challenge name (for example `fyp`, `#fyp`, or a `tiktok.com/tag/...` URL) and returns the public videos for that entity.
- **Sound mode** resolves an exact numeric sound/music ID (or a `tiktok.com/music/...` URL) and returns the public videos that use that sound.

Each run uses exactly one mode, so every dataset row has an explicit, predictable type: `hashtagContent` or `soundContent`. The Actor does not mix different entity types into one undifferentiated dataset.

For every unique public video the default dataset receives one normalized row with:

- the source entity the row was discovered through (hashtag/challenge or sound);
- video ID, canonical public URL, caption/description, and publication time;
- views, likes, comments, shares, and saves when available;
- creator ID, username, display name, and verification state;
- music ID, title, author, duration, and original-sound flag;
- hashtags referenced by the video, video dimensions, and cover URL; and
- an observation timestamp for repeatable tracking.

This product does not download video or audio files and does not return transcript text.

### Input

| Field | Type | Required | Default | Limits | Purpose |
| --- | --- | --- | --- | --- | --- |
| `mode` | string | yes | `hashtag` | `hashtag` or `sound` | Which entity type to discover content for. |
| `targets` | array of strings | yes | — | 1–20 entries; each ≤300 characters | Exact hashtag names / `tiktok.com/tag/...` URLs, or exact numeric sound IDs / `tiktok.com/music/...` URLs. Duplicates are processed once. |
| `maxItems` | integer | no | `1000` | 1–10,000 | Hard cap on unique rows written across all targets. |
| `maxItemsPerTarget` | integer | no | `100` | 1–1,000 | Hard cap on unique rows per individual target. |
| `maxRequests` | integer | no | derived | 1–300 | Hard cap on total upstream requests. Leave empty for a safe derived default. |
| `timeBudgetSecs` | integer | no | `300` | 1–3,300 | Soft wall-clock budget; the run stops safely and reports a partial outcome. |

Valid hashtag input:

```json
{
  "mode": "hashtag",
  "targets": ["fyp", "https://www.tiktok.com/tag/research"],
  "maxItems": 200,
  "maxItemsPerTarget": 100
}
```

Valid sound input:

```json
{
  "mode": "sound",
  "targets": ["7224128604890990593"],
  "maxItems": 100,
  "maxItemsPerTarget": 50
}
```

Targets that do not match the selected mode (for example a music URL in hashtag mode) are rejected before any request, so the Actor never follows an unverified redirect or guesses an entity type.

### Output

Each unique public video creates one paid row in the default dataset. Hashtag mode writes `hashtagContent` rows and sound mode writes `soundContent` rows.

| Field | Type | Nullable | Description |
| --- | --- | --- | --- |
| `recordType` | string | no | `hashtagContent` or `soundContent`. |
| `discoveryMode` | string | no | The requested mode: `hashtag` or `sound`. |
| `query` | string | no | The exact normalized target this row was discovered for. |
| `source` | object | no | The hashtag/challenge or sound/music entity (id, name, description, author, duration, and counts when available). |
| `videoId` | string | no | Stable numeric video ID and dataset identity. |
| `description` | string | yes | Public video caption or description. |
| `createdAt` | string | yes | ISO 8601 publication time when available. |
| `webVideoUrl` | string | yes | Canonical public TikTok video URL when the creator handle is known. |
| `author` | object | no | Creator ID, username, display name, and verification state. |
| `metrics` | object | no | Nullable integer counts for views, likes, comments, shares, and saves. |
| `music` | object | no | Music ID, title, author, duration, and original-sound flag. |
| `hashtags` | array | no | Hashtags referenced by the video. |
| `video` | object | no | Duration, width, height, and cover URL. |
| `isAd` | boolean | yes | Public paid-partnership flag when available. |
| `observedAt` | string | no | UTC time when the row was normalized. |

See `customer/examples/result.json` and `customer/examples/result.csv` for valid illustrative records. The examples validate the schema; they are not live result claims.

### Empty, partial, unavailable, and failed runs

Invalid input stops before any request. Hashtags or sounds that cannot be resolved, and public content that is unavailable, do not become dataset rows and are not charged as content rows. Their safe per-target outcomes remain in the free `RUN_SUMMARY` key-value record.

The summary distinguishes `succeeded`, `partial`, `empty`, `failed`, `charge_limit_reached`, `billing_uncertain`, and `cancelled`, and reconciles delivered versus charged rows:

- If the billing limit prevents a dataset push, no delivered row is counted.
- If a storage or billing response leaves the write outcome uncertain, the Actor stops, does not retry the row, and makes no delivery claim for that write.
- Pagination terminates safely on an absent or repeated cursor instead of looping.

### Pricing

Pay per event. The Actor-start event is charged per GB of run memory, with a minimum of one event; the proposed 256 MB default therefore incurs one start event. A `content-result` event is charged for each unique row successfully delivered to the dataset. Duplicate, empty, unavailable, invalid, and failed results are not charged as content rows.

| Plan | Actor start | Per content row | 1 row | 100 rows |
| --- | ---: | ---: | ---: | ---: |
| Free | $0.0025 | $0.0030 | $0.0055 | $0.3025 |
| Bronze | $0.0023 | $0.0020 | $0.0043 | $0.2023 |
| Silver | $0.0021 | $0.0015 | $0.0036 | $0.1521 |
| Gold | $0.0020 | $0.0010 | $0.0030 | $0.1020 |
| Platinum | $0.0020 | $0.0010 | $0.0030 | $0.1020 |
| Diamond | $0.0020 | $0.0010 | $0.0030 | $0.1020 |

Platform compute usage, if any, is shown separately by Apify.

### Reliability and limits

- Transient rate-limit, server, and timeout failures use bounded retries and honor a short `Retry-After` value when provided.
- Requests are paced conservatively within a run. This does not claim an account-wide distributed rate limit.
- Duplicate targets and duplicate videos are removed by stable ID before delivery.
- Media download URLs, audio download URLs, and private supplier identifiers are intentionally excluded from every row.
- Engagement counts can change after retrieval. Store `videoId` as the stable join key and `observedAt` as the observation time.

### Use from the API

After publication, call the Actor with your Apify token:

```bash
curl "https://api.apify.com/v2/acts/data-slayer~tiktok-content-discovery/runs?token=<YOUR_APIFY_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"mode":"hashtag","targets":["fyp"],"maxItems":100,"maxItemsPerTarget":50}'
```

The proposed slug is not live yet. This endpoint becomes valid only after an authorized release.

### FAQ

**How much does it cost to run this Actor?**
Pricing is listed in the Pricing section above, charged per row returned. You only pay for the rows
the Actor actually produces, so a small test run costs cents.

**Do I need cookies or a login?**
No. This Actor reads public data only - no cookies, no account, no browser automation.

**Can I schedule it?**
Yes. Run it from the Apify API, on a schedule, or via a webhook, and export results as JSON, CSV or Excel.

**What if I need a slightly different output?**
Open an issue on the Actor's Issues tab describing the shape you need.

### Related actors

More Tiktok actors from the same portfolio. Apify only auto-recommends actors in the same category, so here are the rest of ours:

| Actor |
|---|
| [TikTok Comments Scraper · No Cookies](https://apify.com/data-slayer/tiktok-comments) |
| [TikTok Followers Scraper · No Cookies](https://apify.com/data-slayer/tiktok-followers) |
| [TikTok Popular Posts Scraper · No Cookies](https://apify.com/data-slayer/tiktok-popular-posts) |
| [TikTok Profile Scraper · No Cookies](https://apify.com/data-slayer/tiktok-profile-scraper) |
| [TikTok Top Ads Research · Creative Center](https://apify.com/data-slayer/tiktok-top-ads-scraper) |
| [TikTok Video Details Scraper · URL or ID](https://apify.com/data-slayer/tiktok-video-details) |
| [TikTok Video Search Scraper · No Cookies](https://apify.com/data-slayer/tiktok-video-search) |

# Actor input Schema

## `mode` (type: `string`):

Which entity type to discover content for. "hashtag" resolves exact hashtag/challenge names; "sound" resolves exact numeric sound/music IDs. Each mode writes its own explicit row type.

## `targets` (type: `array`):

Exact targets for the selected mode. In hashtag mode add hashtag names or tiktok.com/tag/... URLs (for example "fyp" or "#fyp"). In sound mode add numeric sound/music IDs or tiktok.com/music/... URLs. Duplicate targets are processed once. 1–20 targets.

## `maxItems` (type: `integer`):

Hard cap on unique content rows written to the dataset across all targets. Duplicate videos are never charged.

## `maxItemsPerTarget` (type: `integer`):

Hard cap on unique content rows collected for each individual hashtag or sound. Bounds content depth per entity.

## `maxRequests` (type: `integer`):

Hard cap on total upstream requests for the run. Leave empty to use a safe default derived from the number of targets and rows per target.

## `timeBudgetSecs` (type: `integer`):

Soft wall-clock budget for collection. The run stops safely and reports a partial outcome when the budget is reached.

## Actor input object example

```json
{
  "mode": "hashtag",
  "targets": [
    "fyp"
  ],
  "maxItems": 1000,
  "maxItemsPerTarget": 100,
  "timeBudgetSecs": 300
}
```

# Actor output Schema

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

One paid row per unique public video associated with the requested hashtag/challenge or sound. Hashtag mode writes hashtagContent rows and sound mode writes soundContent rows.

## `runSummary` (type: `string`):

Free run outcome: status, per-target completion, duplicate and error counts, and delivered-versus-charged reconciliation.

# 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 = {
    "targets": [
        "fyp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-slayer/tiktok-content-discovery").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 = { "targets": ["fyp"] }

# Run the Actor and wait for it to finish
run = client.actor("data-slayer/tiktok-content-discovery").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 '{
  "targets": [
    "fyp"
  ]
}' |
apify call data-slayer/tiktok-content-discovery --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-slayer/tiktok-content-discovery"
        }
    }
}
```

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/magrZDQw2ydD3M6Id/builds/cqFinb96yOfZSJzKe/openapi.json
