# TikTok User Search Scraper: TikTok Creator & Influencer Search (`hyperbach/tiktok-search-scraper`) Actor

TikTok user search scraper: find TikTok influencers and creators by keyword. One row per account TikTok's Users tab lists, in TikTok's order, with rank, handle, user id, secUid, bio, emails, verified badge and follower count; up to about 250 a keyword; only new creators on a schedule. No login.

- **URL**: https://apify.com/hyperbach/tiktok-search-scraper.md
- **Developed by:** [Hyperbach](https://apify.com/hyperbach) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 4 total users, 3 monthly users, 97.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 account found by a keywords

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 User Search Scraper: TikTok Creator & Influencer Search

**Find TikTok creators by keyword: every account TikTok's own Users tab lists for a search term, logged out, in TikTok's order — up to about 250 accounts a keyword from Germany, the United Kingdom or France, about 60 to 70 from the United States — one row per account with its rank, handle, user id, secUid, bio, verified badge, follower count and live status.** Give search terms or TikTok search URLs, choose the country the search is read from, cap per keyword or per run. Every keyword ends with a verdict that says whether the list ended at TikTok's login wall, at your cap or at its end — never a short dataset that looks whole. Add each account's own profile page for exact counts, bio link, created date and language (`includeProfileDetails`); **[run it on a schedule with `onlyNew`](https://apify.com/hyperbach/tiktok-search-scraper/examples/monitor-new-tiktok-creators-for-keywords-daily)** and get only the accounts new since the last run — 0 rows and $0 when none. No login, no cookies, proxies handled for you.

### Why this scraper, not the other TikTok user search actors

- **TikTok's own ranking, read from TikTok's own page.** The rows are the accounts TikTok's Users tab lists for the keyword, in TikTok's order, with the position on every row (`rank`): for `notion`, @notion first and @notionhq second on every read of the study, as on tiktok.com. No third-party API in between.
- **Deep where TikTok is deep, honest where it is not.** Logged out, TikTok serves up to about 250 accounts a keyword from Germany, the United Kingdom and France (ten pages of 10, then larger answers until TikTok says the list has no more; 247 and 249 for two keywords walked to the end) and about 60 to 70 from the United States (seven pages of 10; 60 to 70 for six keywords). Every keyword row says where its list ended: COMPLETE at TikTok's own end, CAPPED at your cap, or SOURCE-LIMIT with the count reached when TikTok's page stopped serving earlier. Every depth figure here was measured against TikTok's own page in the same hour. Ready-made: [All TikTok accounts the user search lists for a keyword](https://apify.com/hyperbach/tiktok-search-scraper/examples/all-tiktok-accounts-the-user-search-lists-for-a-keyword).
- **The country is real and on every row.** TikTok ranks accounts per country (German and US reads of `notion` share 6 of their top 10). `readCountry` picks the exit the search is read from — DE, GB, FR or US — and every row carries `read_country`. Ready-made: [TikTok user search from the United States](https://apify.com/hyperbach/tiktok-search-scraper/examples/tiktok-user-search-from-the-united-states).
- **Exact counts, bio link and created date on request.** TikTok's search carries the follower count rounded (117,100 for 117,126) and no bio link. `includeProfileDetails` reads each delivered account's own page: exact followers, following, likes and videos (`counts_precision: exact`), the private flag, the bio link, created date, language, TikTok Shop and business flags — charged per row as `profile_detail`, only when the page was served. Ready-made: [TikTok creators by keyword with exact follower counts](https://apify.com/hyperbach/tiktok-search-scraper/examples/tiktok-creators-by-keyword-with-exact-follower-counts).
- **Exact caps, free misses, filters before the bill.** 25 asked = 25 delivered and charged. A keyword TikTok lists no account for, a keyword stopped before it by your cap, an entry that is not a search: one typed keyword row each, free. Follower range, verified only, an e-mail in the bio, bio keywords: a row left out is not charged. An account found by two keywords of a run is delivered and charged once. Ready-made: [TikTok influencers with 10,000+ followers and an e-mail in bio](https://apify.com/hyperbach/tiktok-search-scraper/examples/tiktok-influencers-with-10k-followers-and-email-in-bio).
- **Only the new accounts since the last run.** With `onlyNew` a scheduled run reads the lists as any run does and delivers only the accounts no earlier run of the same memory delivered, whichever keyword found them; none → 0 rows and nothing charged. The first run is the baseline and says so.
- **Contacts from the bio and TikTok's related searches, free.** Every account row has the e-mails, phone numbers and links found in its bio and whether the account is live right now; every keyword row has TikTok's own search-box suggestions for the keyword from the read country (8 to 10 related searches).

### Who it's for

- **Influencer marketing teams and agencies** — find the creators TikTok itself ranks for a niche keyword in a market, with follower counts, verified badges and bio contacts, ready to shortlist.
- **Brands and growth teams** — watch a set of keywords on a schedule and get only the accounts that newly rank for them, country by country.
- **Lead generation and outreach** — accounts in a niche filtered to a follower range, verified only or an e-mail in the bio, with the contacts extracted and the profile page's bio link on request.
- **Researchers and data teams** — TikTok's user-search ranking per keyword and country with stable ids (user id, secUid), the rank, the page and an honest end-of-list verdict, ready to join across runs.

### Quick start

**The accounts TikTok lists for a keyword (up to about 250 from Germany)**

```json
{
  "keywords": [
    "notion"
  ]
}
```

**Several keywords from the United Kingdom, 50 accounts each**

```json
{
  "keywords": [
    "notion templates",
    "productivity app",
    "study planner"
  ],
  "readCountry": "GB",
  "maxUsersPerKeyword": 50
}
```

**The US list for a keyword (about 60 to 70 accounts)**

```json
{
  "keywords": [
    "skincare routine"
  ],
  "readCountry": "US"
}
```

**Creators with 10,000+ followers, with their exact counts, bio link and created date**

```json
{
  "keywords": [
    "fitness coach"
  ],
  "minFollowers": 10000,
  "includeProfileDetails": true,
  "maxUsersPerKeyword": 100
}
```

**Only the accounts new since the last run (schedule it)**

```json
{
  "keywords": [
    "notion"
  ],
  "onlyNew": true
}
```

### Output

One row per account TikTok's Users tab lists for a keyword (row_type user), and one free row per keyword and read country with its status, verdict and TikTok's own suggestions (row_type keyword):

| field | meaning |
|---|---|
| `row_type` | `user` for an account TikTok's Users tab listed for a keyword; `keyword` for the free row per keyword and read country with its status, verdict and counts. |
| `keyword` | The search term TikTok was asked, as typed (a URL's `q` parameter read out). |
| `input` | The `keywords` entry this row came from, verbatim (a term or a TikTok search URL). |
| `read_country` | The country TikTok's answer was read from (`readCountry`: DE, GB, FR or US); TikTok ranks accounts per country. Null when your own proxy carried the browser. |
| `rank` | The account's position in TikTok's Users-tab order for the keyword and country, 1 = first (the stage-1 reference: @notion rank 1, @notionhq rank 2 for `notion`). |
| `page` | Which answer of TikTok's list carried the account (1 = the first page of 10; the last answer carries the remaining rows at once). |
| `search_id` | TikTok's own id of the search answer (`log_pb.impr_id`), the same for every page of one keyword's walk. |
| `scraped_at` | When the row was read (UTC). |
| `user_id` | TikTok's numeric account id, as a string (19 digits; stable across handle changes). Join and deduplicate on this. |
| `sec_uid` | TikTok's secondary id of the account (`MS4wLjABAAAA…`), the key its list and video routes take. |
| `username` | The account's handle (`uniqueId`), without the @. |
| `nickname` | The account's display name. |
| `bio` | The account's bio text (`signature`), as TikTok's search carries it. |
| `bio_emails` | E-mail addresses found in the bio text (extraction only; no contact finding). |
| `bio_phones` | Phone numbers found in the bio text (8–15 digits; dates left out). |
| `bio_links` | Links and domains found in the bio text. |
| `profile_url` | `https://www.tiktok.com/@<username>`. |
| `avatar_thumb_url` | The account's avatar at 100 px, as TikTok's search carries it (a signed CDN URL that expires). |
| `avatar_url` | The account's avatar at 1080 px, from its profile page (`includeProfileDetails`); null otherwise. |
| `verified` | `true` when TikTok's search marks the account verified (an enterprise or custom verification). |
| `verify_reason` | TikTok's verification text (`verified account`, or a custom one); null for an unverified account. |
| `is_live` | `true` when the account was live at the read (a live room id on the search record). |
| `followers_shown` | The follower count as TikTok's search shows it, rounded (117,100 for 117,126). Every row has it. |
| `likes_shown` | The account's total likes as TikTok's search shows them (`total_favorited`). Every row has it. |
| `followers` | The exact follower count from the account's own profile page (`statsV2`; `includeProfileDetails`); null otherwise. |
| `following` | The exact following count from the profile page (`includeProfileDetails`); null otherwise. |
| `likes` | The exact like count from the profile page (`includeProfileDetails`); null otherwise. |
| `videos` | The exact video count from the profile page (`includeProfileDetails`); null otherwise. |
| `friends` | The mutual-follow count from the profile page (`includeProfileDetails`); null otherwise. |
| `counts_precision` | `as_shown`: the counts are the rounded ones TikTok's search shows; `exact`: the profile page's exact counts are on the row (`includeProfileDetails`). |
| `private_account` | Whether the account is private, from its profile page (`includeProfileDetails`); null otherwise (TikTok's search carries no private flag). |
| `profile_detail_status` | With `includeProfileDetails`: `ok` (the page was read and the fields filled, charged as `profile_detail`), `not_found`, `unavailable`, `region_restricted` or `failed` (not charged); null when the add-on is off. |
| `created_at` | When the account was created (UTC), from its profile page (`includeProfileDetails`). |
| `language` | The account's language code from its profile page (`includeProfileDetails`). TikTok shows no region to a logged-out reader. |
| `bio_link` | The link under the bio, from the profile page (`includeProfileDetails`). |
| `business_category` | The business account's category, from the profile page (`includeProfileDetails`); null for a personal account. |
| `is_organization` | Whether the account is an organization, from the profile page (`includeProfileDetails`). |
| `tt_seller` | Whether the account sells on TikTok Shop, from the profile page (`includeProfileDetails`). |
| `first_seen_at` | With `onlyNew`: the day an earlier run of the same memory first saw the account (the run's day for a new one); null otherwise. |
| `run_tag` | Your `runTag`, copied onto every row. |
| `status` | Keyword rows: `ok` (accounts served), `empty` (TikTok lists no account for the keyword), `failed` (TikTok's page served no answer to the browser: the run is PARTIAL or FAILED and says so), `not_read` (maxItems or the charge limit was reached before it), `invalid_input` (the entry is not a search). |
| `status_detail` | Keyword rows: the verdict in words — how many accounts TikTok serves from the read country and where the list ended, the cap, the filters' count, the baseline note. |
| `coverage` | Keyword rows: `SOURCE-LIMIT` (TikTok stopped serving at its login wall; `users_served` is the count reached), `COMPLETE` (TikTok said the list ended, or lists no account), `CAPPED` (your cap or charge limit), `NEW-ONLY` (an onlyNew run after the baseline), `PARTIAL` (the walk ended early). |
| `users_served` | Keyword rows: unique accounts TikTok served for the keyword before caps, filters and dedupe. |
| `users_delivered` | Keyword rows: `user` rows delivered and charged for the keyword. |
| `users_filtered` | Keyword rows: accounts the filters left out (not charged). |
| `users_duplicate` | Keyword rows: accounts delivered under an earlier keyword of the same run (`dedupeAcrossKeywords`; not charged again). |
| `users_known` | Keyword rows: accounts an earlier run of the same memory delivered (`onlyNew`; skipped, not charged). |
| `pages` | Keyword rows: answers with accounts TikTok's page made for the keyword. |
| `wall` | Keyword rows: what TikTok's page showed when it stopped — `login-wall` (the end of a served list: the Log in button), `login-modal`, `server-error`, `slider`; null while the list was still being served. |
| `suggestions` | Keyword rows: TikTok's own search-box suggestions for the keyword from the read country (8–10 related searches), free. |
| `seconds` | Keyword rows: how long the keyword's read took. |
| `only_new` | Keyword rows: whether the run was an `onlyNew` run. |

Example record:

```json
{
  "row_type": "user",
  "keyword": "notion",
  "input": "notion",
  "read_country": "DE",
  "rank": 1,
  "page": 1,
  "search_id": "20261009015420802B00B3C5DE659B8715",
  "scraped_at": "2026-10-09T01:54:27Z",
  "user_id": "7038607187375490054",
  "sec_uid": "MS4wLjABAAAAppNu6Z_NxLOj_Gug2559GgdCKAnKi_6ajwmKXcdhhewVLynkjgw7blJ9Feu5a9sM",
  "username": "notion",
  "nickname": "NOTION",
  "bio": "You Heard It Here First\nPresenting the freshest faces in music & beyond\nLDN & LA",
  "bio_emails": [],
  "bio_phones": [],
  "bio_links": [],
  "profile_url": "https://www.tiktok.com/@notion",
  "avatar_thumb_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-useast2a-avt-0068-euttp/51a05d2551085bb758b2f6d6864a4a84~tplv-tiktokx-cropcenter:100:100.webp?biz_tag=tiktok_user.user_cover&dr=10399&idc=no1a&ps=13740610&refresh_token=d53537d1&shcp=c1333099&shp=30310797&t=4d5b0474&x-expires=1791594000&x-signature=Jd%2B%2FLSVlNjHGkNPoCe0g2v9%2BSKs%3D",
  "avatar_url": null,
  "verified": true,
  "verify_reason": "verified account",
  "is_live": false,
  "followers_shown": 117100,
  "likes_shown": 11725489,
  "followers": null,
  "following": null,
  "likes": null,
  "videos": null,
  "friends": null,
  "counts_precision": "as_shown",
  "private_account": null,
  "profile_detail_status": null,
  "created_at": null,
  "language": null,
  "bio_link": null,
  "business_category": null,
  "is_organization": null,
  "tt_seller": null,
  "first_seen_at": null,
  "run_tag": null,
  "status": null,
  "status_detail": null,
  "coverage": null,
  "users_served": null,
  "users_delivered": null,
  "users_filtered": null,
  "users_duplicate": null,
  "users_known": null,
  "pages": null,
  "wall": null,
  "suggestions": null,
  "seconds": null,
  "only_new": null
}
```

### Pricing

**Pay only for the accounts a run delivers**, with no start fee and no platform usage billed to you. Prices fall with your Apify plan.

| per 1,000 | Free | Starter | Scale | Business |
|---|---|---|---|---|
| Account found by a keyword (`user`) | $2.00 | $1.70 | $1.70 | $1.70 |
| Profile details on that row (`profile_detail`) | +$1.00 | +$0.85 | +$0.70 | +$0.50 |

An account row is **$2.00 per 1,000 rows** on the Free plan and $1.70 on Business: a keyword walked to the end of TikTok's list (up to about 250 accounts) is at most $0.50 on Free; 50 accounts are $0.10. The `+` fee is charged **only on a row that carries the thing**: with `includeProfileDetails`, a row that carries the account's own profile page ($1.00 per 1,000 such rows on the Free plan); a page TikTok did not serve is said on the row and not charged. Free: the keyword row of every search (its status, verdict and TikTok's suggestions), a keyword TikTok lists no account for, rows the filters leave out, an account already delivered under an earlier keyword of the run, a run with `onlyNew` that finds nothing new (0 rows, $0), and a run that failed before its first account. A run that stops early (a timeout, an abort) is charged for the accounts it delivered. `maxItems` and `maxUsersPerKeyword` are exact; before the first request the log says the most the run can cost at your plan's prices. Enterprise plans have their own rates — the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Creator discovery by keyword** — Give the terms in `keywords` as you would type them into TikTok's search box (a `#tag` is searched as text; a TikTok search URL gives its term). Rows come in TikTok's order with `rank`, `page` and `search_id`; the keyword row says how many accounts TikTok served and where the list ended (`coverage`, `status_detail`). Several terms in one run give each account once, under the first keyword that found it (`dedupeAcrossKeywords`). Ready-made: [TikTok creator search across several keywords, deduplicated](https://apify.com/hyperbach/tiktok-search-scraper/examples/tiktok-creator-search-across-keywords-deduplicated).
- **Per country** — `readCountry` is the country the search is read from: DE (default), GB and FR serve up to about 250 accounts a keyword, US about 60 to 70. Run the same keywords once per country to compare the views; every row carries `read_country`.
- **Filters that run before the bill** — `minFollowers` / `maxFollowers` (on the count TikTok's search shows, rounded; the exact count is checked too with `includeProfileDetails`), `verifiedOnly`, `withEmailOnly`, `bioKeywords` (bio, display name or handle). Rows left out are not charged and do not count against the caps; the keyword row counts them (`users_filtered`). With `verifiedOnly` and one account a keyword, a list of brand names gives each brand's verified account. Ready-made: [Find the official TikTok account of each brand](https://apify.com/hyperbach/tiktok-search-scraper/examples/find-the-official-tiktok-account-of-each-brand).
- **Exact counts and the profile page** — `includeProfileDetails` reads each delivered account's own profile page (one request per row): `followers`, `following`, `likes`, `videos` exact, `private_account`, `bio_link`, `created_at`, `language`, `business_category`, `is_organization`, `tt_seller`, the large avatar. Charged per row as `profile_detail` when the page was served (`profile_detail_status: ok`).
- **Monitoring: only new accounts** — `onlyNew` with a `stateStoreName` keeps the account ids delivered under that name on the Actor's own memory. A later run with the same name delivers only the accounts not delivered before and says NEW-ONLY on the keyword row; nothing new → 0 rows, $0. The first run with the switch on is the baseline. With `maxUsersPerKeyword` the same top N is compared on every run. Ready-made: [Monitor new TikTok creators for your keywords, daily](https://apify.com/hyperbach/tiktok-search-scraper/examples/monitor-new-tiktok-creators-for-keywords-daily).
- **The found accounts' videos and followers** — Every row carries the account's `username`, `user_id` and `sec_uid`, the keys our other TikTok Actors take, with the same column names: give them to the [TikTok Profile Scraper](https://apify.com/hyperbach/tiktok-profile-scraper) for each account's exact counts, its newest videos or its whole video history, and to the [TikTok Followers Scraper](https://apify.com/hyperbach/tiktok-followers-scraper) for its followers and following lists. The rows join on `user_id` with no mapping.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `keywords` | `array` | `[]` | Search terms, one per line, as you would type them into TikTok's search box: a word or phrase (notion templates), a #tag (searched as text), or a TikTok search URL (https://www.tiktok.com/search/user?q=notion). Case does not matter; a term given twice is read once. |
| `readCountry` | `DE` / `GB` / `FR` / `US` | `"DE"` | The country the search is read from. TikTok's Users tab ranks accounts per country and, logged out, serves up to about 250 accounts a keyword from Germany, the United Kingdom and France and about 60 to 70 from the United States; every row says which country read it (read_country). |
| `maxUsersPerKeyword` | `integer` | `0` | How many accounts to deliver per keyword, in TikTok's order; 0 = everything TikTok serves logged out (up to about 250 from DE, GB or FR, about 60 to 70 from US; the keyword row then says COMPLETE at TikTok's end of the list, or SOURCE-LIMIT with the count when TikTok's page stopped serving earlier). Exact: 25 asked = 25 delivered and charged. With onlyNew the cap is the depth read (the top 25 are compared with the memory on every run). A run that reads past 100 accounts a keyword gets 4,096 MB by default, others 2,048 MB. |
| `minFollowers` | `integer` | `0` | Deliver only accounts with at least this many followers; 0 = no filter. |
| `maxFollowers` | `integer` | `0` | Deliver only accounts with at most this many followers; 0 = no filter. |
| `verifiedOnly` | `boolean` | `false` | Deliver only accounts with TikTok's verified badge. |
| `withEmailOnly` | `boolean` | `false` | Deliver only accounts whose bio carries an e-mail address (bio_emails). Extraction from the bio text only; no contact finding. |
| `bioKeywords` | `array` | `[]` | Deliver only accounts whose bio, display name or handle contains one of these words (case-insensitive). Empty = no filter. |
| `dedupeAcrossKeywords` | `boolean` | `true` | An account found by a second keyword of the same run is delivered once (under the first keyword) and not charged again; the keyword row counts it (users_duplicate). Off: every keyword delivers its own full list. |
| `includeProfileDetails` | `boolean` | `false` | Read each delivered account's own profile page too (one request per row) for its exact follower, following, like and video counts, bio link, created date, language, private flag and TikTok Shop and business flags. Charged per row as a profile_detail event, besides the row. TikTok shows no region to a logged-out reader, so none is given. |
| `onlyNew` | `boolean` | `false` | Deliver only the accounts no earlier run of the same memory delivered, whichever keyword found them, and nothing when none is new (0 rows, $0). The first run with this on delivers the lists as the baseline. The memory is kept under stateStoreName, or under one default memory of your Apify account. With maxUsersPerKeyword the same top N is compared on every run. |
| `stateStoreName` | `string` |  | The name of the memory onlyNew uses between runs (the account ids delivered). Empty = one default memory for your Apify account; give a name to keep two tasks apart. |
| `maxItems` | `integer` | `0` | Stop after this many charged account rows across all keywords; 0 = no limit. Free rows (the keyword rows) do not count. The run also stops inside your maximum charge and says so. |
| `runTag` | `string` |  | A label copied onto every row (run_tag), to tell runs apart in a shared dataset. |
| `proxyUrl` | `string` |  | Empty (recommended): TikTok is read through the Actor's own residential exit in the country of readCountry, at no extra cost to you. Your own proxy URL (scheme://user:pass@host:port) carries the browser instead; the read country is then your proxy's. The word none runs without any proxy, from the Apify worker's own IP (for tests; TikTok may refuse a datacenter address). |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

A keyword TikTok lists no account for is not an error: it gets one free keyword row that says so (status empty), and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Why does a keyword take a minute, when other search actors answer in seconds?**

Because TikTok's search lists are signed by its own page and bound to the exit that loaded it: no plain request serves them. This Actor loads TikTok's own search page in a real browser through a residential exit of your country and reads the page's own answers while it scrolls; the first keyword of a run takes one to three minutes (the browser starts and loads TikTok's page twice), each further keyword about a minute for 50 accounts, and a walk to the end of a list (about 250 accounts) 6 to 10 minutes, because TikTok sends its last answers about half a minute apart. The other actors that answer in seconds either read TikTok's app API with a signer they do not publish, or rent a third-party API.

**Why up to about 250 accounts a keyword, and fewer from the United States?**

That is where TikTok's list ends for a logged-out reader: from Germany, the United Kingdom and France the Users tab pages by scroll, ten accounts a page and then larger answers, until TikTok answers that the list has no more (247 and 249 accounts for two keywords walked to the end); from the United States the list ends after about 60 to 70 accounts. When TikTok's page pauses before that, the Actor scrolls again and reloads the page rather than stopping at the first pause. The keyword row says where the list ended (`coverage`: COMPLETE, or SOURCE-LIMIT when the page stopped serving earlier; `users_served`). No login is used or asked for.

**Why is the follower count rounded, and how do I get the exact one?**

TikTok's search carries the counts as the app shows them (117,100 for 117,126): `followers_shown` and `likes_shown` on every row. `includeProfileDetails` reads each account's own profile page and fills `followers`, `following`, `likes` and `videos` exactly (`counts_precision: exact`), plus the bio link, created date and language.

**Can it return the videos, top results or live rooms for a keyword?**

Not in this version. Logged out, TikTok serves one page of 12 videos a keyword from a few countries and a login prompt after it, at a cost per row far above what video search actors charge; this Actor is the Users tab. For a creator's videos use the [TikTok Profile Scraper](https://apify.com/hyperbach/tiktok-profile-scraper) with the `username` or `sec_uid` of a row; for its followers the [TikTok Followers Scraper](https://apify.com/hyperbach/tiktok-followers-scraper).

**Do the rows differ by country?**

Yes: TikTok ranks accounts per country. German and US reads of `notion` share 6 of their top 10; the top two were the same on every read. Choose `readCountry` for the market you care about and compare runs by `read_country`.

**What does a failed keyword look like, and what am I charged?**

A keyword TikTok's page did not serve to the browser (a server-error view, a login modal on an exit) is retried once in a fresh browser on a fresh exit; a keyword the server-error view kept from its first read is read once more after the other keywords. An exit TikTok places in another country than `readCountry` is replaced; when no exit in that country is found, the keyword row says whose view its list is. If a keyword is still not served, its keyword row says `status: failed` with the reason, the run ends PARTIAL (rows out) or FAILED (none) and says which keywords are missing. You pay for the account rows delivered and nothing else; a run that failed before its first account charges nothing.

**Do I need a proxy or a TikTok account?**

No. The Actor reads TikTok logged out through its own residential exit in the country of `readCountry`, included in the price. `proxyUrl` is there for buyers who must use their own proxy; the read country is then the proxy's.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/tiktok-search-scraper').call({"keywords": ["notion"]});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/tiktok-search-scraper').call(run_input={'keywords': ['notion']})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/tiktok-search-scraper --input '{"keywords": ["notion"]}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~tiktok-search-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"keywords": ["notion"]}'
```

### Support

apify@hyperbach.com

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

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

Search terms, one per line, as you would type them into TikTok's search box: a word or phrase (notion templates), a #tag (searched as text), or a TikTok search URL (https://www.tiktok.com/search/user?q=notion). Case does not matter; a term given twice is read once.

## `readCountry` (type: `string`):

The country the search is read from. TikTok's Users tab ranks accounts per country and, logged out, serves up to about 250 accounts a keyword from Germany, the United Kingdom and France and about 60 to 70 from the United States; every row says which country read it (read_country).

## `maxUsersPerKeyword` (type: `integer`):

How many accounts to deliver per keyword, in TikTok's order; 0 = everything TikTok serves logged out (up to about 250 from DE, GB or FR, about 60 to 70 from US; the keyword row then says COMPLETE at TikTok's end of the list, or SOURCE-LIMIT with the count when TikTok's page stopped serving earlier). Exact: 25 asked = 25 delivered and charged. With onlyNew the cap is the depth read (the top 25 are compared with the memory on every run). A run that reads past 100 accounts a keyword gets 4,096 MB by default, others 2,048 MB.

## `minFollowers` (type: `integer`):

Deliver only accounts with at least this many followers; 0 = no filter.

## `maxFollowers` (type: `integer`):

Deliver only accounts with at most this many followers; 0 = no filter.

## `verifiedOnly` (type: `boolean`):

Deliver only accounts with TikTok's verified badge.

## `withEmailOnly` (type: `boolean`):

Deliver only accounts whose bio carries an e-mail address (bio_emails). Extraction from the bio text only; no contact finding.

## `bioKeywords` (type: `array`):

Deliver only accounts whose bio, display name or handle contains one of these words (case-insensitive). Empty = no filter.

## `dedupeAcrossKeywords` (type: `boolean`):

An account found by a second keyword of the same run is delivered once (under the first keyword) and not charged again; the keyword row counts it (users_duplicate). Off: every keyword delivers its own full list.

## `includeProfileDetails` (type: `boolean`):

Read each delivered account's own profile page too (one request per row) for its exact follower, following, like and video counts, bio link, created date, language, private flag and TikTok Shop and business flags. Charged per row as a profile_detail event, besides the row. TikTok shows no region to a logged-out reader, so none is given.

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

Deliver only the accounts no earlier run of the same memory delivered, whichever keyword found them, and nothing when none is new (0 rows, $0). The first run with this on delivers the lists as the baseline. The memory is kept under stateStoreName, or under one default memory of your Apify account. With maxUsersPerKeyword the same top N is compared on every run.

## `stateStoreName` (type: `string`):

The name of the memory onlyNew uses between runs (the account ids delivered). Empty = one default memory for your Apify account; give a name to keep two tasks apart.

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

Stop after this many charged account rows across all keywords; 0 = no limit. Free rows (the keyword rows) do not count. The run also stops inside your maximum charge and says so.

## `runTag` (type: `string`):

A label copied onto every row (run_tag), to tell runs apart in a shared dataset.

## `proxyUrl` (type: `string`):

Empty (recommended): TikTok is read through the Actor's own residential exit in the country of readCountry, at no extra cost to you. Your own proxy URL (scheme://user:pass@host:port) carries the browser instead; the read country is then your proxy's. The word none runs without any proxy, from the Apify worker's own IP (for tests; TikTok may refuse a datacenter address).

## Actor input object example

```json
{
  "keywords": [
    "notion"
  ],
  "readCountry": "DE",
  "maxUsersPerKeyword": 50,
  "minFollowers": 0,
  "maxFollowers": 0,
  "verifiedOnly": false,
  "withEmailOnly": false,
  "bioKeywords": [],
  "dedupeAcrossKeywords": true,
  "includeProfileDetails": false,
  "onlyNew": false,
  "maxItems": 100
}
```

# Actor output Schema

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

All scraped records in the default dataset. One row per account TikTok's Users tab lists for a keyword (row_type user), and one free row per keyword and read country with its status, verdict and TikTok's own suggestions (row_type keyword):

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

How the run went, per keyword: status, accounts served, delivered, filtered, already delivered and known, pages, the wall TikTok showed, TikTok's suggestions, seconds, the verdict in words; why the run stopped early, if it did; and the source counters (exit checks, browser loads, browsers started, search answers, profile pages).

# 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 = {
    "keywords": [
        "notion"
    ],
    "maxUsersPerKeyword": 50,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/tiktok-search-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 = {
    "keywords": ["notion"],
    "maxUsersPerKeyword": 50,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/tiktok-search-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 '{
  "keywords": [
    "notion"
  ],
  "maxUsersPerKeyword": 50,
  "maxItems": 100
}' |
apify call hyperbach/tiktok-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hyperbach/tiktok-search-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/ghFs1OHV6ni4UXyL2/builds/j0oMrfzv73RzFactL/openapi.json
