# TikTok Keyword Search — Keyword & Hashtag Videos, Transcripts (`steadyfetch/tiktok-keyword-search`) Actor

TikTok keyword and hashtag search, priced per video, no login, no cookies: one row per video — caption, hashtags, plays, likes, comments, shares, saves, creator handle, sound, posted time and link. Optional transcript per video. Re-runs return only videos new since your last run.

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

## Pricing

from $0.40 / 1,000 videos

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 Keyword Search — Keyword & Hashtag Videos, Transcripts

**Type a keyword or a hashtag and get one row per TikTok video: caption, hashtags, plays, likes, comments, shares, saves, the creator's handle, the sound, when it was posted and its link — plus, if you ask, what each video says. No login, no cookies, no start fee — from $0.40 per 1,000 videos, and a re-run returns only the videos new since your last run.**

Put search phrases ("sourdough bread") or hashtags ("sourdough") in the lists and the actor returns the videos TikTok shows for each, in TikTok's own order, 10 to 30 at a time. Built for trend watchers, creator researchers and social-listening teams who want TikTok search as data, and want to keep watching it.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/tiktok-keyword-search`

- **Actor id:** `steadyfetch/tiktok-keyword-search`
- **Input:** `{ "keywords": ["sourdough bread"], "hashtags": ["sourdough"], "maxResults": 50, "transcripts": false }`
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `1.00` — the run stops when it reaches it.
- **Price:** from $0.0004 per video ($0.0006 on the Apify free plan); a page that brings fewer videos than $0.0065 is worth is billed one $0.0065 page check instead; a transcript from $0.01, only when real text lands. Full table below.
- **How often it changes:** TikTok search moves every day. Schedule the same keywords daily or weekly: with "Only new videos" on (the default) each run returns, and charges, only the videos you have not had yet.

*Unofficial, not affiliated with TikTok.*

### What you get

One row per video:

| Column | What it holds |
|---|---|
| `videoId` · `videoUrl` | the video's id and its TikTok page |
| `caption` · `hashtags` · `language` | the caption as posted, its hashtags (lower-cased), and the caption's language |
| `createTime` · `durationSeconds` | when it was posted (UTC) and how long it runs |
| `plays` · `likes` · `comments` · `shares` · `saves` | TikTok's displayed counts at the moment of the read |
| `authorHandle` · `authorId` | the creator's public handle and numeric id |
| `musicTitle` · `musicAuthor` · `musicOriginal` | the sound's title, its artist for a library track, and whether it is the creator's own sound |
| `isAd` · `isPaidPartnership` · `mediaType` | TikTok's ad and paid-partnership flags, and video or photo post |
| `coverUrl` · `hasCaptions` | the cover image (TikTok's signed link — it expires, so save the image promptly) and whether TikTok has a caption track |
| `query` · `queryType` · `rank` · `searchPage` | what you searched (keyword or hashtag) and the video's place in TikTok's list |
| `seenBefore` | `true` when your account received this video for this search before |
| `transcript` · `transcriptSource` · `transcriptLanguage` · `transcriptSegments` | with "Transcripts" on: what the video says, from `captions` (TikTok's own track) or `speech` (speech-to-text), with timestamps |
| `transcriptStatus` · `transcriptNote` | why a transcript is empty, when it is (no speech, too long, already had, not asked for) |

Every row also carries `charged`, `chargedEvents`, `status`, `retryable` and `statusReason`, so you can reconcile your bill from the dataset itself. The last row (`run_summary`) is the receipt.

**About the counts.** Plays, likes, comments, shares and saves are the numbers TikTok shows at the moment of the read; TikTok rounds and recounts them, so treat them as a reading, not a ledger. **Creators by handle only:** no display names, avatars or bios are copied, and an original sound's author (the creator's display name) is left empty.

### How to use it

1. Put search phrases in **Keywords** and/or hashtags in **Hashtags**, one per line ("#" optional). Up to 50 searches per run. A pasted TikTok search address (`tiktok.com/search?q=…`) or tag address (`tiktok.com/tag/…`) is read as its search.
2. Set **Max videos per search**. Switch on **Transcripts** if you want what each video says.
3. Press Start — or schedule it: with "Only new videos" on, every later run brings only the videos new to each search.

Refused before anything is read, and never charged: a creator's profile link, a single video's link, any other address, a hashtag with spaces, and a value with no letters or numbers. **Want every video of one creator?** Use [TikTok Transcript Scraper — TikTok Subtitles to Text, No Login](https://apify.com/steadyfetch/tiktok-profile-transcripts), which takes a handle.

One search is read to at most 500 videos (20 pages) per run.

### Pricing

Pay per result, all-inclusive (no platform usage on top), no start fee:

| Event | Apify Free plan | Bronze | Silver | Gold and up |
|---|---|---|---|---|
| Video (one delivered video) | $0.0006 | $0.0005 | $0.00045 | $0.0004 |
| Page check | $0.0065 | $0.0065 | $0.0065 | $0.0065 |
| Video transcript (only with "Transcripts" on, only when real text lands) | $0.015 | $0.014 | $0.0125 | $0.01 |

That is $0.40 per 1,000 videos at Gold.

**The page check.** TikTok answers a search 10 to 30 videos at a time, and each page is read whole, whatever it holds. A page bills **either** its videos **or** one check — whichever is more — never both. A full page of 30 videos at $0.0004 is $0.012 and pays no check. A page with fewer videos than one check is worth — a short page, **the last page your own limit cuts short**, a page of videos your account already had — is billed one $0.0065 check on its own row, and its rows carry no video fee. **A keyword TikTok finds nothing matching:** TikTok still answers with unrelated videos; when none of them carries a word of your keyword, they are not delivered, and the page is billed one check. A hashtag TikTok has no exact match for costs nothing.

Examples at Gold: 60 videos of one keyword (pages of 30 · 10 · 29) = 50 × $0.0004 + one check = $0.0265. The same keyword with transcripts on, 40 of the 60 videos speaking = $0.0265 + 40 × $0.01 = $0.4265.

### Transcripts

- **TikTok's own captions first.** Most videos carry a caption track (about 6 in 10, measured); those transcripts come straight from it, any length.
- **Speech-to-text for the rest**, on videos up to 120 seconds. A longer video without captions gets an empty transcript and no transcript fee.
- **Charged only when real text lands.** Music-only and silent videos are never sold as transcripts; the row says why the transcript is empty.
- A video your account already had is handed back without transcribing it again (and without a transcript fee).
- On the Apify free plan, a run makes at most 5 transcripts from audio; captioned videos are not limited.

### Good to know

- **Only new videos (default on).** Videos this actor delivered to your account for the same keyword or hashtag in the last 90 days are skipped and not charged. A re-run stops after 3 pages in a row with nothing new (`maxEmptyPages`, up to 20), so it costs at most 3 page checks ($0.0195). Turn it off to get every video again with today's counts — each charged, and marked `seenBefore: true`.
- **Your memory lives in your account:** the list of videos delivered to you sits in a key-value store in your own Apify account (`tiktok-search-account`). If the run's API token cannot open stores, the run still works, every video reads as new, and a row says so.
- **Pages vary:** a page can hold 10 or 30 videos; a short page is not the end of the list. TikTok sometimes lists a video twice; it is delivered once.
- **TikTok's own order:** results come in the order TikTok's search ranks them. Date and sort filters are not offered — TikTok returned the same list for them.
- **How long it takes:** a page takes 3–6 seconds; a transcript up to a minute.
- **A time limit ends the collecting, never the delivering**: rows already read are delivered, and the receipt says which limit stopped the run.
- **Paused for now:** if collection on this actor is paused, the rest of a search ships as one uncharged `try_again` row — run the same search again later.
- **Free plan:** on the Apify free plan a run reads up to 25 pages per 24 hours; the row says when it resets. Paid plans have no daily allowance.
- **Standby endpoint:** one keyword or hashtag is also available as one HTTP request (`GET /?keyword=sourdough%20bread` or `GET /?hashtag=sourdough`), up to 60 videos a request (20 on the free plan), at the same prices. The endpoint keeps no account memory (every video is new to it) and makes no transcripts — both are run features.
- **A bare Start** (nothing named) returns 10 dated sample videos for "sourdough bread" and charges nothing.

### Support

Questions, a search that returns something odd, or a column you need: open an issue on the Issues tab and it will be looked at.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Changelog

This Actor's version history is a separate document: https://apify.com/steadyfetch/tiktok-keyword-search/changelog.md

# Actor input Schema

## `keywords` (type: `array`):

One search phrase per line, exactly as you would type it into TikTok's search ("sourdough bread"). TikTok answers every phrase with its own matches, in its own order (rank). A pasted TikTok search address (tiktok.com/search?q=…) is read as its phrase. Up to 50 searches per run, keywords and hashtags together. A pasted profile or video link gets its own uncharged row naming the actor that takes it.

## `hashtags` (type: `array`):

One hashtag per line, with or without "#" ("sourdough"). Returns the videos TikTok files under that exact hashtag. A pasted tag address (tiktok.com/tag/…) is read as its name. A hashtag TikTok has no exact match for gets one uncharged row and costs nothing.

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

The most videos to return for each keyword and each hashtag. A page carries 10–30 videos; one search is read to at most 500 videos (20 pages) per run. A page that brings fewer videos than one page check is worth — often the last page your limit cuts — is billed one check instead of per video.

## `transcripts` (type: `boolean`):

On: each delivered video also carries what it says — TikTok's own caption track when it has one, otherwise speech-to-text from the audio (videos up to 120 seconds; on the Apify free plan up to 5 a run from audio). Charged as its own "Video transcript" fee, only when real text lands; a video with no speech, a longer uncaptioned video, or one you already had is an uncharged empty transcript. Off (default): no transcript, no transcript fee.

## `onlyNew` (type: `boolean`):

On (default): videos this actor already delivered to your account for the same keyword or hashtag in the last 90 days are skipped and not charged, so a scheduled run returns only videos new to the search (a page of nothing new bills one page check). Off: every run returns the search's videos with today's counts, each charged, and a video delivered before carries seenBefore: true.

## `maxEmptyPages` (type: `integer`):

With "Only new videos" on: a search stops after this many pages in a row that bring no new video, so a re-run that finds nothing new costs at most this many page checks. Default 3; up to 20.

## `maxRunSeconds` (type: `integer`):

Stop collecting after this many seconds. Videos already collected are still delivered. Leave empty for no limit beyond the run timeout.

## Actor input object example

```json
{
  "maxResults": 50,
  "transcripts": false,
  "onlyNew": true,
  "maxEmptyPages": 3
}
```

# Actor output Schema

## `rows` (type: `string`):

One row per TikTok video of each keyword or hashtag you named: the search and its type, the video's rank in TikTok's list, video id and link, caption, hashtags, caption language, posted time, length, plays, likes, comments, shares and saves (TikTok's displayed figures at read time), the creator's public handle and id, the sound's title (and its artist for a library track), whether the sound is the creator's own, whether TikTok marks it an ad or a paid partnership, and the cover link (TikTok's signed link, which expires — save it promptly). With "transcripts" on, each row also carries `transcript` (the text), `transcriptSource` (captions or speech), `transcriptLanguage`, `transcriptSegments` (timestamped) and `transcriptStatus` — an empty transcript says why in `transcriptNote` and is never charged. Every row carries `charged`, `chargedEvents` and `status`, so the invoice reconciles from the dataset itself. A video row is charged once, as a `video`; its transcript once more, as a `transcript`, only when real text landed. A page that brings fewer videos than one page check is worth (a short page, the last page your limit cuts, a page of videos your account already had with onlyNew on, a keyword TikTok finds nothing matching) is billed one `page-check` on its own row instead, and its rows carry no video fee.

## `misses` (type: `string`):

One uncharged row for each search that returned less than you asked for, saying why: `no_data` (TikTok lists fewer videos than your limit, or found nothing matching a keyword — its unrelated videos are not delivered), `skipped.all_repeat` (onlyNew on: several pages in a row held only videos your account already had — raise "maxEmptyPages" to look further), `skipped.page_cap` (one search is read to at most 20 pages per run), `input_error` (the entry is a link, not a search, or a hashtag TikTok has no exact match for — the row names the fix), `feed_unavailable` (TikTok did not answer — please re-run), `try_again` (collection on this actor is paused for now — run the same search again later), `limit_reached` (this run reached its collection limit — the row gives the next step), `skipped_max_items` / `skipped_budget` / `skipped_deadline` (your own limit, maximum charge or the run clock stopped it first) and `skipped.free_plan_allowance` (Apify free plan only: the plan's daily allowance on this actor; the row names the reset time).

## `summary` (type: `string`):

The run's receipt: videos delivered, what was asked for, pages read, transcripts made (and how many came from TikTok's own captions), videos skipped because your account already had them, the charged events and what stopped the run. Uncharged.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/tiktok-keyword-search").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/tiktok-keyword-search").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 '{}' |
apify call steadyfetch/tiktok-keyword-search --silent --output-dataset

```

## MCP server setup

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

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/Fa4m5wkosb2aKT1ih/builds/RzpX3cHHEH57YyFH0/openapi.json
