# TikTok User Profile Scraper — TikTok Followers, Videos, Stats (`hyperbach/tiktok-profile-scraper`) Actor

Scrape TikTok user profiles by username, URL, user id or secUid: exact follower count, likes and video count, user id, secUid, created date, bio link, plus each profile's videos with plays and captions, followers and following lists. TikTok stats and profile data as an API. No login.

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

## Pricing

from $1.90 / 1,000 profiles

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 Profile Scraper — TikTok Followers, Videos, Stats

**TikTok profiles with the exact counts — 143,661 followers, not 143,700 — next to the rounded ones, plus the user id, secUid, created date, bio link, business category, verified and private flags, in one row per account.** Give handles, profile or video URLs, user ids, secUids or share links. Add each profile's videos (the 10 newest, or the whole history with a COMPLETE or TRUNCATED verdict against TikTok's own count), reposts, stories, playlists, followers and following. Every row says which country read it, because TikTok shows each country different numbers. **Accounts that do not exist, are private or have no videos come back as a typed row, free.** **[Monitor accounts on a schedule](https://apify.com/hyperbach/tiktok-profile-scraper/examples/monitor-tiktok-accounts-daily-only-what-changed)** and get only what changed. No login, no cookies, proxies handled for you.

### Why this scraper, not the other TikTok profile actors

- **Exact counts, both numbers.** TikTok's page carries the exact follower, like and video counts next to the rounded ones the app shows. The lane leader returns the rounded ones (143,700 for Notion's 143,661 on 2026-10-01) and no secUid. Each profile row here has `followers` (exact) and `followers_shown` (as the app shows it), the same for likes, following and videos, with `counts_precision` saying which you got.
- **The whole record in one row.** User id, secUid, handle, name, bio, bio link, avatar in three sizes, verified, private, organization, business category, TikTok Shop seller, language, created date, the dates the handle and the name last changed, live and story flags, and the account's comment, duet, stitch and download settings. Ids are strings everywhere, music ids included (several rivals float-cast them).
- **Labelled by the country that read it.** TikTok shows each country its own view: Notion had 331 videos for a US reader and 330 for a German one on the same minute. Every row says `read_country`, and `readCountries` gives you one labelled row per country you ask for.
- **Honest rows.** An account that does not exist, is private, is hidden in the read country, or has no videos gives one typed row with `profile_status` and a reason, and is not charged. A run TikTok refused ends PARTIAL or FAILED and says why, never "0 rows, SUCCEEDED".
- **Whole video histories, with a verdict.** Ask for more videos than the account has and the run walks its list to the end: Notion's 331 videos in 72 seconds. The profile row says COMPLETE, TRUNCATED, PARTIAL or CAPPED, with the unique videos seen against TikTok's own count. Latest, Popular or Oldest order, a date window, pinned videos left out if you want. Up to the 10 newest are read without a browser.
- **Every video field the web shows.** Plays, likes, comments, shares, saves, reposts; caption, hashtags, mentions with the mentioned account's id and handle; music with string ids; duration, size, definition, covers; caption tracks and the transcript from them; the tagged place; the country it was posted from; TikTok's own content category; ad, branded-content, Spark Ads, slideshow, Shop and AI-generated flags; the author's exact counts from the same run.
- **The lists around a profile.** Followers and following (each with its own exact counts), reposts, the liked tab where it is public, active stories, playlists; a playlist or collection URL gives its videos.
- **Monitoring that keeps history.** With `onlyChanged` a profile is delivered and charged only when a count, the handle, the bio or another watched field moved since the last delivery; each row carries the previous counts, the change and the change per day, and a count history keyed by the user id, so a renamed account keeps its history (`previous_handle`). `onlyNewVideos` skips videos delivered before.
- **Engagement from the run's own rows.** Engagement rate per view and per follower, average and median plays, posts per week, posting hours and weekdays, top hashtags, TikTok's content categories, and a creator score, each with its formula below, computed from the videos the same run delivered. Free.
- **Drop-in for the leader's field names.** `outputFormat: clockworks` returns profile and video rows in the field names of clockworks/tiktok-profile-scraper (`authorMeta`, `diggCount`, `webVideoUrl`, ...), so a pipeline built on it reads this one unchanged, with exact counts and handle-based mention links.

### Who it's for

- **Influencer marketing teams and agencies** — vet creators with exact follower counts, the engagement of their recent videos, their posting rhythm, business category and verification, and track them weekly with a history of counts.
- **Brands and growth teams** — follow competitors' accounts: new videos as they appear, growth per day, renames, and which videos travel.
- **Researchers and data teams** — the exact record with stable ids (user id, secUid, string video and music ids), labelled by the country that read it, ready to join across runs.
- **Developers moving off another actor** — the leader's field names with exact counts, typed error rows and a run summary, without a minimum charge.

### Quick start

**One profile, exact counts**

```json
{
  "profiles": [
    "notionhq"
  ]
}
```

**A list of creators with their 10 newest videos**

```json
{
  "profiles": [
    "notionhq",
    "duolingo",
    "https://www.tiktok.com/@mrbeast"
  ],
  "maxVideosPerProfile": 10
}
```

**A whole video history, with the coverage verdict**

```json
{
  "profiles": [
    "notionhq"
  ],
  "maxVideosPerProfile": 10000
}
```

**The US and the German view of the same accounts**

```json
{
  "profiles": [
    "notionhq",
    "duolingo"
  ],
  "readCountries": [
    "US",
    "DE"
  ]
}
```

**Followers and following**

```json
{
  "profiles": [
    "notionhq"
  ],
  "maxFollowersPerProfile": 200,
  "maxFollowingPerProfile": 200
}
```

**A daily monitor: only what changed**

```json
{
  "profiles": [
    "notionhq",
    "duolingo"
  ],
  "maxVideosPerProfile": 10,
  "onlyChanged": true,
  "onlyNewVideos": true
}
```

### Output

One row per profile and read country, one per video, follower or following account, and playlist (row\_type says which):

| field | meaning |
|---|---|
| `row_type` | profile, video, user (a follower or following account) or playlist. |
| `input` | What you gave in profiles, verbatim. |
| `profile_status` | ok, private (counts shown, videos not), not\_found (no such account: the handle is free, or the account was banned or deleted), region\_restricted (TikTok hides the account from readers in the read country), invalid\_input (the entry is not a TikTok account; the others in the run are still read) or failed. Only ok rows with videos are charged. |
| `status_detail` | Why a profile is not ok, in words, with TikTok's own status code; on an ok row, a read that is not quite what was asked (TikTok answered for another region than the read country). |
| `read_country` | The country the row was read from (readCountries). TikTok shows counts and videos by the reader's country. |
| `page_region` | The region TikTok's page says it served (webapp.app-context.region). |
| `scraped_at` | When the row was read (UTC). |
| `user_id` | TikTok's numeric user id, as a string. Stable across renames. |
| `sec_uid` | TikTok's secUid, the id its own APIs take. |
| `username` | The handle, without @. |
| `nickname` | The display name. |
| `bio` | The profile text. |
| `bio_link` | The link set in the profile; null when none. |
| `bio_link_risk` | TikTok's own risk score of the bio link (bioLink.risk; 0 = no risk seen); null when no link. |
| `profile_url` | https://www.tiktok.com/@<username>. |
| `avatar_url` | The avatar at 1080 px (an expiring TikTok link). |
| `avatar_medium_url` | The avatar at 720 px. |
| `avatar_thumb_url` | The avatar at 100 px. |
| `verified` | TikTok's verified badge. |
| `private_account` | The account is private. |
| `is_organization` | TikTok marks the account as an organization. |
| `business_category` | The category of a business account ("Shopping & Retail"). |
| `commerce_user` | A business (commerce) account. |
| `tt_seller` | The account sells on TikTok Shop. |
| `language` | The account's language as TikTok records it. |
| `created_at` | When the account (profile rows) or the video (video rows) was created (UTC). |
| `username_modified_at` | When the handle was last changed (uniqueIdModifyTime). TikTok gives 0 to a logged-out reader for most accounts (every read in stage 4), so null does not mean never renamed. |
| `nickname_modified_at` | When the display name was last changed (nickNameModifyTime); null when TikTok gives 0, which does not mean never. |
| `followers` | Followers, exact (TikTok's statsV2), for the read country. |
| `following` | Accounts followed, exact. |
| `likes` | Likes received (hearts) on a profile, exact; likes of the video on a video row. |
| `videos` | The account's video count as TikTok shows it to the read country. |
| `friends` | Mutual follows. |
| `liked_videos` | Videos the account has liked. |
| `followers_shown` | Followers as the app rounds them (143,700 for 143,661). |
| `following_shown` | Following as the app shows it. |
| `likes_shown` | Likes as the app rounds them. |
| `videos_shown` | Videos as the app shows them. |
| `counts_precision` | exact (TikTok's exact counts), as\_shown (only the rounded ones were served), or on videos exact\_plays\_rounded (TikTok's web rounds plays even in its exact counts). |
| `live_room_id` | The live room id while the account is live. |
| `is_live` | The account is live at read time. |
| `has_story` | The account has an active story. |
| `comment_setting` | everyone, friends or nobody. |
| `duet_setting` | everyone, friends or nobody. |
| `stitch_setting` | everyone, friends or nobody. |
| `download_setting` | everyone, friends or nobody. |
| `following_visibility` | public, private or friends. |
| `liked_public` | The account shows the videos it liked. |
| `embed_allowed` | The account allows its profile to be embedded. |
| `playlists_tab` | The profile shows a Playlists tab. |
| `videos_requested` | maxVideosPerProfile for this run. |
| `videos_delivered` | The profile's own videos this run delivered. |
| `videos_route` | embed (the 10 newest, each from its video page) or browser (the profile's video list walked in a browser). |
| `videos_coverage` | COMPLETE (everything asked, or every video TikTok lists), TRUNCATED (TikTok stopped serving before), PARTIAL (a read failed; the rest is delivered), CAPPED (maxItems or your maximum charge cut it). |
| `videos_note` | How the walk went: unique videos seen against TikTok's count, list pages, why it stopped. |
| `engagement_videos` | The delivered posts the engagement figures are computed from (at least 3). |
| `engagement_rate` | Mean over the videos of (likes + comments + shares + saves) / plays, in percent. |
| `engagement_rate_followers` | Mean (likes + comments + shares + saves) per video / followers, in percent. |
| `avg_plays` | Mean plays of those videos. |
| `median_plays` | Median plays of those videos. |
| `avg_likes` | Mean likes of those videos. |
| `avg_comments` | Mean comments of those videos. |
| `avg_shares` | Mean shares of those videos. |
| `avg_saves` | Mean saves of those videos. |
| `posts_per_week` | Those videos divided by the weeks between the oldest and the newest of them. |
| `creator_score` | 0-100: 40 x reach (log10 followers / 8) + 40 x engagement (rate / 10 %) + 20 x activity (posts per week / 7), each capped at 1. |
| `top_hashtags` | The hashtags of those videos, most used first: \[{hashtag, videos}]. |
| `top_content_categories` | TikTok's own content category codes of those videos, most frequent first: \[{category, videos}]. |
| `posting_hours_utc` | Videos per hour of the day: \[{hour, videos}]. |
| `posting_weekdays_utc` | Videos per weekday: \[{weekday, videos}]. |
| `followers_delivered` | Follower rows this run delivered for the profile. |
| `following_delivered` | Following rows delivered. |
| `reposts_delivered` | Repost rows delivered. |
| `liked_delivered` | Liked-video rows delivered. |
| `stories_delivered` | Story rows delivered. |
| `playlists_delivered` | Playlist rows delivered. |
| `change_type` | With a monitoring memory: new (first delivery), changed or unchanged since the last delivery. |
| `changed_fields` | The watched fields that moved since the last delivery. |
| `previous_read_at` | When the memory last delivered this account. |
| `previous_followers` | Followers at the previous delivery. |
| `followers_change` | Followers now minus at the previous delivery. |
| `followers_change_per_day` | That change per day since the previous delivery. |
| `likes_change` | Likes now minus at the previous delivery. |
| `videos_change` | Videos now minus at the previous delivery. |
| `previous_handle` | The handle the memory knew this user id by, when it changed. |
| `first_read_at` | When the memory first read this account. |
| `times_read` | How many runs read this account into the memory. |
| `count_history` | The memory's reads of this account, newest last: \[{at, followers, following, likes, videos}] (up to 30). |
| `media_files` | Files this row stored in the run's key-value store (downloadMedia): \[{kind, key, url, bytes, reused}]. |
| `avatar_file_url` | The avatar in the run's key-value store: the link works as long as the store is kept (Apify's data retention for the run's default store). |
| `run_tag` | runTag, copied onto every row. |
| `video_source` | posts, reposts, liked, story, playlist or collection. |
| `source_route` | video\_page (the video's own page), browser\_list (the profile's list in a browser), open\_list (TikTok's open lists) or embed (only the embed's fields: the video page failed). |
| `playlist_id` | The playlist of the video, or of the playlist row. |
| `video_id` | TikTok's video id, as a string. |
| `video_url` | https://www.tiktok.com/@<username>/video/<id> (photo for slideshows). |
| `caption` | The video's caption. |
| `text_language` | The caption's language as TikTok detects it. |
| `plays` | Plays as TikTok's web shows them (TikTok rounds plays even in its exact counts). |
| `comments` | Comments, exact. |
| `shares` | Shares, exact. |
| `saves` | Saves (favourites), exact. |
| `reposts` | Reposts, exact. |
| `is_pinned` | Pinned to the profile (known on the browser route; null on the others). |
| `is_ad` | TikTok marks the video as an ad. |
| `is_branded_content` | The creator's paid-partnership (branded content) toggle is on. |
| `ad_authorization` | The creator authorized the video for Spark Ads. |
| `is_slideshow` | A photo slideshow, not a video. |
| `has_shop_product` | A TikTok Shop product is attached. |
| `is_ai_generated` | TikTok's AI-generated content label. |
| `ai_label` | TikTok's AI-generated content description. |
| `is_private_video` | The video itself is private. |
| `duration_s` | Video length in seconds. |
| `width` | Video width in pixels. |
| `height` | Video height in pixels. |
| `definition` | 720p, 1080p, ... |
| `video_format` | The file format (mp4). |
| `size_bytes` | The video file's size. |
| `bitrate` | The video bitrate. |
| `cover_url` | The cover at full size (an expiring TikTok link). |
| `cover_720_url` | The cover at 720 px (AVIF). |
| `dynamic_cover_url` | The animated cover. |
| `play_url` | TikTok's play link (signed, expiring; it serves only the session that read the page). |
| `download_url` | TikTok's download link (signed, expiring). |
| `media_expires_at` | When the soonest of the row's TikTok media links stops working (UTC). |
| `caption_tracks` | TikTok's caption tracks: \[{language, format, source (ASR = automatic, MT = machine translation), version, url, expires\_at}]. |
| `transcript` | The spoken text from the caption track (includeTranscripts). |
| `transcript_language` | The caption track's language. |
| `music_id` | The sound's id, as a string. |
| `music_title` | The sound's title. |
| `music_author` | The sound's author. |
| `music_original` | The sound is the creator's own. |
| `music_url` | The sound's audio link. |
| `music_cover_url` | The sound's cover image (TikTok's link, expires). |
| `music_duration_s` | The sound's length. |
| `hashtags` | Hashtags of the caption (full rows: names; clockworks rows: \[{name}]). |
| `mentions` | Accounts mentioned in the caption (full rows: \[{user\_id, username, sec\_uid}]; clockworks rows: \[@username]). |
| `slideshow_images` | The images of a slideshow, in order. |
| `anchors` | Links TikTok attaches to the video (an app, an effect, a product): \[{type, kind, name}]. |
| `poi_id` | The tagged place's id. |
| `poi_name` | The tagged place's name. |
| `poi_address` | The tagged place's address. |
| `poi_city` | The tagged place's city. |
| `poi_category` | The tagged place's category. |
| `location_created` | The country TikTok records the video was posted from (video page route). |
| `content_category` | TikTok's own content category code (CategoryType). |
| `content_labels` | TikTok's own content labels (diversificationLabels), when given. |
| `duet_enabled` | Duets are allowed on this video. |
| `stitch_enabled` | Stitches are allowed. |
| `share_enabled` | Sharing is allowed. |
| `comments_enabled` | Comments are open. |
| `can_repost` | The video can be reposted. |
| `author_id` | The author's user id. |
| `author_username` | The author's handle. |
| `author_nickname` | The author's display name. |
| `author_verified` | The author's verified badge. |
| `author_followers` | The author's followers: exact, from this run's own read of the profile when the run read it. |
| `author_followers_shown` | The author's followers as the app rounds them. |
| `author_likes` | The author's likes received. |
| `author_videos` | The author's video count. |
| `cover_file_url` | The cover (720 px) or the first slideshow image in the run's store. |
| `video_file_url` | The video file in the run's store (downloadMedia: videos). |
| `relation` | On user rows: follower (follows of\_username) or following (of\_username follows them). |
| `of_user_id` | The profile whose list this row comes from. |
| `of_username` | That profile's handle. |
| `playlist_name` | The playlist's name. |
| `playlist_url` | The playlist's page. |
| `playlist_video_count` | Videos in the playlist. |
| `rowType` | clockworks shape: profile or video. |
| `id` | clockworks shape: the video id. |
| `text` | clockworks shape: the caption. |
| `textLanguage` | clockworks shape: the caption's language. |
| `createTime` | clockworks shape: the video's creation time, unix seconds. |
| `createTimeISO` | clockworks shape: the video's creation time (UTC). |
| `isAd` | clockworks shape: an ad. |
| `isPinned` | clockworks shape: pinned (browser route). |
| `isSponsored` | clockworks shape: branded content or Spark Ads authorized. |
| `isSlideshow` | clockworks shape: a slideshow. |
| `playCount` | clockworks shape: plays. |
| `diggCount` | clockworks shape: likes, exact. |
| `commentCount` | clockworks shape: comments. |
| `shareCount` | clockworks shape: shares. |
| `collectCount` | clockworks shape: saves. |
| `repostCount` | clockworks shape: reposts. |
| `detailedMentions` | clockworks shape: \[{id, name, profileUrl}] — the profile URL uses the handle. |
| `musicMeta` | clockworks shape: {musicId, musicName, musicAuthor, musicOriginal, playUrl, coverMediumUrl, originalCoverMediumUrl}. |
| `videoMeta` | clockworks shape: {height, width, duration, coverUrl, originalCoverUrl, definition, format, subtitleLinks \[{language, downloadLink, tiktokLink, source, sourceUnabbreviated, version}], aiVideoDescription, aiVideoSummary, transcriptionLink (null)}. |
| `webVideoUrl` | clockworks shape: the video's URL. |
| `mediaUrls` | clockworks shape: stored video files. |
| `authorMeta` | clockworks shape: the profile's 22 keys (id, secUid, name, nickName, signature, bioLink, avatar, fans, heart, video, following, friends, digg, createTime as unix seconds, ... — exact counts); on a video row the author's full record when the run read that profile. |
| `fromProfileSection` | clockworks shape: profile, videos (the profile's own), reposts, liked, story, playlist or collection. |
| `isStory` | clockworks shape: a story. |
| `readCountry` | clockworks shape: the read country. |
| `profileStatus` | clockworks shape: the profile status (ok, private, not\_found, region\_restricted, failed). |
| `hasTikTokShopProduct` | clockworks shape: the video links a TikTok Shop product. |
| `effectStickers` | clockworks shape: always empty (the web record has none). |
| `commentsDatasetUrl` | clockworks shape: always null (comments are not read). |
| `shortDramaSeriesInfo` | clockworks shape: always null. |

Example record:

```json
{
  "row_type": "profile",
  "input": "notionhq",
  "profile_status": "ok",
  "status_detail": null,
  "read_country": "DE",
  "page_region": "DE",
  "scraped_at": "2026-10-01T15:31:38Z",
  "user_id": "6907114026586719238",
  "sec_uid": "MS4wLjABAAAAbLlR9hDWKZO75J9zJW_R9qPrgxTPRM6AH3uypENETAPOA-Dgn_H7OMFZdIcoDHda",
  "username": "notionhq",
  "nickname": "Notion",
  "bio": "The AI workspace that works while you sleep.",
  "bio_link": null,
  "bio_link_risk": null,
  "profile_url": "https://www.tiktok.com/@notionhq",
  "avatar_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-maliva-avt-0068/d449c7f981ba8532f4178cd294ba3666~tplv-tiktokx-cropcenter:1080:1080.jpeg?dr=10399&refresh_token=f51e2e71&x-expires=1791039600&x-signature=xpOo3CralPUf4vMh9o%2FuksHdEMo%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=no1a",
  "avatar_medium_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-maliva-avt-0068/d449c7f981ba8532f4178cd294ba3666~tplv-tiktokx-cropcenter:720:720.jpeg?dr=10399&refresh_token=59513cc6&x-expires=1791039600&x-signature=5gEoNP%2BdqpiVeWkrI7DXzmbSoGM%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=no1a",
  "avatar_thumb_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-maliva-avt-0068/d449c7f981ba8532f4178cd294ba3666~tplv-tiktokx-cropcenter:100:100.jpeg?dr=10399&refresh_token=b927feac&x-expires=1791039600&x-signature=xaa7gEJXWievAMXahtbAGzq4Ceg%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=no1a",
  "verified": true,
  "private_account": false,
  "is_organization": true,
  "business_category": null,
  "commerce_user": false,
  "tt_seller": false,
  "language": "en",
  "created_at": "2021-01-05T22:34:05Z",
  "username_modified_at": null,
  "nickname_modified_at": "2021-01-05T22:36:21Z",
  "followers": 143667,
  "following": 79,
  "likes": 735367,
  "videos": 330,
  "friends": 67,
  "liked_videos": 0,
  "followers_shown": 143700,
  "following_shown": 79,
  "likes_shown": 735400,
  "videos_shown": 330,
  "counts_precision": "exact",
  "live_room_id": null,
  "is_live": false,
  "has_story": false,
  "comment_setting": "everyone",
  "duet_setting": "everyone",
  "stitch_setting": "everyone",
  "download_setting": "everyone",
  "following_visibility": "public",
  "liked_public": false,
  "embed_allowed": true,
  "playlists_tab": false,
  "videos_requested": 10000,
  "videos_delivered": 331,
  "videos_route": "browser",
  "videos_coverage": "COMPLETE",
  "videos_note": "browser walk (latest): 331 unique of 330 TikTok counts, 21 list pages, stopped: met, 71.2 s",
  "engagement_videos": 331,
  "engagement_rate": 4.159,
  "engagement_rate_followers": 1.7699,
  "avg_plays": 56926,
  "median_plays": 14900,
  "avg_likes": 2220,
  "avg_comments": 23.5,
  "avg_shares": 69.9,
  "avg_saves": 229.5,
  "posts_per_week": 1.51,
  "creator_score": 47,
  "top_hashtags": [
    {
      "hashtag": "notion",
      "videos": 251
    },
    {
      "hashtag": "notionapp",
      "videos": 218
    },
    {
      "hashtag": "notiontok",
      "videos": 191
    },
    {
      "hashtag": "productivity",
      "videos": 37
    },
    {
      "hashtag": "notionai",
      "videos": 29
    },
    {
      "hashtag": "notiontemplate",
      "videos": 28
    },
    {
      "hashtag": "blocktok",
      "videos": 23
    },
    {
      "hashtag": "backtoschool",
      "videos": 18
    },
    {
      "hashtag": "ai",
      "videos": 16
    },
    {
      "hashtag": "organization",
      "videos": 11
    }
  ],
  "top_content_categories": [
    {
      "category": 120,
      "videos": 171
    },
    {
      "category": 118,
      "videos": 49
    },
    {
      "category": 105,
      "videos": 33
    },
    {
      "category": 110,
      "videos": 22
    },
    {
      "category": 116,
      "videos": 13
    }
  ],
  "posting_hours_utc": [
    {
      "hour": 0,
      "videos": 27
    },
    {
      "hour": 1,
      "videos": 7
    },
    {
      "hour": 2,
      "videos": 1
    },
    {
      "hour": 3,
      "videos": 1
    },
    {
      "hour": 4,
      "videos": 1
    },
    {
      "hour": 9,
      "videos": 1
    },
    {
      "hour": 12,
      "videos": 2
    },
    {
      "hour": 13,
      "videos": 2
    },
    {
      "hour": 14,
      "videos": 1
    },
    {
      "hour": 15,
      "videos": 8
    },
    {
      "hour": 16,
      "videos": 9
    },
    {
      "hour": 17,
      "videos": 32
    },
    {
      "hour": 18,
      "videos": 42
    },
    {
      "hour": 19,
      "videos": 25
    },
    {
      "hour": 20,
      "videos": 37
    },
    {
      "hour": 21,
      "videos": 35
    },
    {
      "hour": 22,
      "videos": 41
    },
    {
      "hour": 23,
      "videos": 59
    }
  ],
  "posting_weekdays_utc": [
    {
      "weekday": "Mon",
      "videos": 56
    },
    {
      "weekday": "Tue",
      "videos": 77
    },
    {
      "weekday": "Wed",
      "videos": 75
    },
    {
      "weekday": "Thu",
      "videos": 69
    },
    {
      "weekday": "Fri",
      "videos": 49
    },
    {
      "weekday": "Sat",
      "videos": 4
    },
    {
      "weekday": "Sun",
      "videos": 1
    }
  ],
  "followers_delivered": null,
  "following_delivered": null,
  "reposts_delivered": null,
  "liked_delivered": null,
  "stories_delivered": null,
  "playlists_delivered": null,
  "change_type": null,
  "changed_fields": null,
  "previous_read_at": null,
  "previous_followers": null,
  "followers_change": null,
  "followers_change_per_day": null,
  "likes_change": null,
  "videos_change": null,
  "previous_handle": null,
  "first_read_at": null,
  "times_read": null,
  "count_history": null,
  "media_files": null,
  "avatar_file_url": null,
  "run_tag": null,
  "video_source": null,
  "source_route": null,
  "playlist_id": null,
  "video_id": null,
  "video_url": null,
  "caption": null,
  "text_language": null,
  "plays": null,
  "comments": null,
  "shares": null,
  "saves": null,
  "reposts": null,
  "is_pinned": null,
  "is_ad": null,
  "is_branded_content": null,
  "ad_authorization": null,
  "is_slideshow": null,
  "has_shop_product": null,
  "is_ai_generated": null,
  "ai_label": null,
  "is_private_video": null,
  "duration_s": null,
  "width": null,
  "height": null,
  "definition": null,
  "video_format": null,
  "size_bytes": null,
  "bitrate": null,
  "cover_url": null,
  "cover_720_url": null,
  "dynamic_cover_url": null,
  "play_url": null,
  "download_url": null,
  "media_expires_at": null,
  "caption_tracks": null,
  "transcript": null,
  "transcript_language": null,
  "music_id": null,
  "music_title": null,
  "music_author": null,
  "music_original": null,
  "music_url": null,
  "music_cover_url": null,
  "music_duration_s": null,
  "hashtags": null,
  "mentions": null,
  "slideshow_images": null,
  "anchors": null,
  "poi_id": null,
  "poi_name": null,
  "poi_address": null,
  "poi_city": null,
  "poi_category": null,
  "location_created": null,
  "content_category": null,
  "content_labels": null,
  "duet_enabled": null,
  "stitch_enabled": null,
  "share_enabled": null,
  "comments_enabled": null,
  "can_repost": null,
  "author_id": null,
  "author_username": null,
  "author_nickname": null,
  "author_verified": null,
  "author_followers": null,
  "author_followers_shown": null,
  "author_likes": null,
  "author_videos": null,
  "cover_file_url": null,
  "video_file_url": null,
  "relation": null,
  "of_user_id": null,
  "of_username": null,
  "playlist_name": null,
  "playlist_url": null,
  "playlist_video_count": null,
  "rowType": null,
  "id": null,
  "text": null,
  "textLanguage": null,
  "createTime": null,
  "createTimeISO": null,
  "isAd": null,
  "isPinned": null,
  "isSponsored": null,
  "isSlideshow": null,
  "playCount": null,
  "diggCount": null,
  "commentCount": null,
  "shareCount": null,
  "collectCount": null,
  "repostCount": null,
  "detailedMentions": null,
  "musicMeta": null,
  "videoMeta": null,
  "webVideoUrl": null,
  "mediaUrls": null,
  "authorMeta": null,
  "fromProfileSection": null,
  "isStory": null,
  "readCountry": null,
  "profileStatus": null,
  "hasTikTokShopProduct": null,
  "effectStickers": null,
  "commentsDatasetUrl": null,
  "shortDramaSeriesInfo": null
}
```

### Pricing

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

| event | per | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Profile (`profile`) | 1,000 | $2.50 | $2.125 | $1.90 | $1.90 |
| Video (`video`) | 1,000 | $2.00 | $1.80 | $1.80 | $1.80 |
| Follower or following account (`user`) | 1,000 | $3.00 | $2.55 | $2.10 | $1.50 |
| Transcript on that video (`transcript`) | 1,000 | +$0.50 | +$0.425 | +$0.35 | +$0.25 |
| Stored image (`media_file`) | 1,000 | $1.40 | $1.40 | $1.40 | $1.40 |
| Stored video (`media_video`) | one | $0.15 | $0.15 | $0.15 | $0.15 |
| Video list walked in a browser (`video_walk`) | one | $0.41 | $0.41 | $0.41 | $0.41 |

A profile row is **$2.50 per 1,000** on the Free plan; with its 10 newest videos it costs $0.0225. More than the 10 newest videos, Popular or Oldest order, a date window past the newest ten, or `excludePinned` need one browser walk of the profile's list, charged once per profile ($0.41) and only when the walk delivers a video. Free: accounts that do not exist, are private, are hidden in the read country, could not be read or have no videos; playlist rows; a profile skipped by `onlyChanged`; a file an earlier run stored (linked, not charged again); a video without a caption track is not charged a transcript; any run that failed. `maxItems` is exact and never overshot. Enterprise plans have their own rates; the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Profile lookups at scale** — Give thousands of handles, URLs or ids in `profiles` with `maxVideosPerProfile: 0`: one request per account, one row each, typed rows for the ones that are gone or private. `maxItems` caps the charged rows exactly. Ready-made: [TikTok follower counts for a list of accounts](https://apify.com/hyperbach/tiktok-profile-scraper/examples/tiktok-follower-counts-for-a-list-of-accounts).
- **Creator vetting** — Add `maxVideosPerProfile: 10` or more: the profile row gets the engagement figures, posting rhythm and creator score from those videos, and each video row carries the author's exact counts from the same run. Ready-made: [Vet TikTok creators: engagement rate, score, transcripts](https://apify.com/hyperbach/tiktok-profile-scraper/examples/vet-tiktok-creators-engagement-rate-and-transcripts).
- **Full histories and back-catalogue analysis** — Set `maxVideosPerProfile` above the account's video count. The list is walked in a browser to its end, and `videos_coverage` with `videos_note` say whether every video TikTok counts was delivered. Use `videoOrder: oldest` to start from the first video, or `videosNewerThan` / `videosOlderThan` for a window. Ready-made: [A TikTok account's full video history, with a verdict](https://apify.com/hyperbach/tiktok-profile-scraper/examples/tiktok-account-full-video-history).
- **Monitoring on a schedule** — Save a task with `onlyChanged` and `onlyNewVideos` and run it daily: unchanged profiles and old videos are not delivered or charged; changed profiles carry the change since the last delivery and the history of counts. Apify's own webhooks and integrations on the task send the result to Slack, email or a sheet. One thing to know: TikTok serves two US views of the exact counts from two data centres, up to 0.007% apart in the same minute (MrBeast: about 10,000 followers), so a change of that size on a large account can be a switch of view, not a real change; the row's change per day shows which. Ready-made: [Monitor TikTok accounts daily: only what changed](https://apify.com/hyperbach/tiktok-profile-scraper/examples/monitor-tiktok-accounts-daily-only-what-changed).
- **Audience lists** — `maxFollowersPerProfile` and `maxFollowingPerProfile` return the accounts around a profile, newest first, each with its own exact counts, linked back with `of_user_id`. Ready-made: [TikTok followers and following of an account](https://apify.com/hyperbach/tiktok-profile-scraper/examples/tiktok-followers-and-following-of-an-account).
- **Regional comparisons** — `readCountries` reads each account from each country you list. The rows are labelled, so the two views never mix.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `profiles` | `array` | `[]` | TikTok accounts, one per line, in any form: a handle (notionhq or @notionhq), a profile URL, a video or photo URL (its author is read), a numeric user id, a secUid, a share link (vm.tiktok.com/…), or a playlist or collection URL (its videos are read). Handles are case-insensitive; an account named twice is read once. |
| `readCountries` | `array` | `["US"]` | The country each account is read from. TikTok shows the counts and the videos of the reader's country (Notion: 331 videos to a US reader, 330 to a German one), so every row says which country read it. Two countries give two labelled profile rows per account; videos and lists are read from the first country. |
| `maxVideosPerProfile` | `integer` | `0` | How many of each profile's own videos to deliver; 0 = the profile row only. Up to 10 newest are read from TikTok's embed and each video's page; more are read by walking the profile's video list in a browser, to the end if you ask for more than the account has, with a COMPLETE or TRUNCATED verdict against TikTok's own count. A browser walk (more than 10, Popular or Oldest order, a date window past the newest 10, excludePinned) is charged once per profile as a video\_walk event, besides the videos. |
| `videoOrder` | `latest` / `popular` / `oldest` | `"latest"` | Which end of the video list to read from: TikTok's own Latest, Popular and Oldest tabs. |
| `videosNewerThan` | `string` |  | Only videos posted on or after this date: 2026-05-01, or a span back from today such as 30 days, 2 weeks, 6 months, 1 year (UTC). |
| `videosOlderThan` | `string` |  | Only videos posted before this date: 2026-06-01, or a span back from today (UTC). |
| `excludePinned` | `boolean` | `false` | Skip the videos the account pinned to the top of its profile. Read in the browser, where TikTok marks them. |
| `includeTranscripts` | `boolean` | `false` | The spoken text of each video, from TikTok's own caption track (automatic or the creator's), in the video's language. A video without a caption track gets none; no AI transcription. |
| `downloadMedia` | `none` / `images` / `videos` | `"none"` | Copy files into the run's key-value store, so the rows carry links that work as long as the store is kept (Apify's data retention for a run's default store; TikTok's own links expire within hours to days). Charged per file written. |
| `maxRepostsPerProfile` | `integer` | `0` | Videos the profile reposted, newest first, as video rows. |
| `maxLikedPerProfile` | `integer` | `0` | Videos the profile liked, where the account makes its liked tab public (most do not: the profile row's liked\_public says which). |
| `includeStories` | `boolean` | `false` | The profile's active stories as video rows (video\_source story), when it has any. |
| `includePlaylists` | `boolean` | `false` | One free row per playlist of the profile (name, video count, URL). Give a playlist URL in Profiles for its videos. |
| `maxFollowersPerProfile` | `integer` | `0` | Accounts that follow the profile, newest follower first, each with its own exact counts. |
| `maxFollowingPerProfile` | `integer` | `0` | Accounts the profile follows, where the account shows them. |
| `outputFormat` | `full` / `clockworks` | `"full"` | Full gives flat rows with every field below. clockworks gives profile and video rows in the field names of the most used TikTok profile actor, so a pipeline built on it reads this one unchanged — with exact counts instead of rounded ones. |
| `maxItems` | `integer` | `0` | Stop after this many charged rows (profiles, videos and users together); 0 = no limit. Free rows (accounts not found, private, empty) do not count. The run also stops inside your maximum charge and says so. |
| `onlyChanged` | `boolean` | `false` | Monitoring: deliver (and charge) a profile only when a count, the handle, the bio or another watched field changed since the last run that delivered it. Each row carries the previous counts, the change and the change per day. |
| `onlyNewVideos` | `boolean` | `false` | Monitoring: skip videos an earlier run of the same task already delivered. |
| `stateStoreName` | `string` |  | The name of the memory that keeps each account's counts between runs (a count history on every row, keyed by the user id, so a renamed account keeps its history). Set by itself from the accounts when onlyChanged or onlyNewVideos is on. |
| `runTag` | `string` |  | A label copied onto every row (run\_tag), to tell runs apart in a shared dataset. |
| `resumeFromRunId` | `string` |  | The id of an earlier run of this actor with the same input that stopped before the end: this run reads only the accounts it had not finished, and delivers nothing twice. |

### 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 search that matches nothing is not an error: the dataset is empty and nothing is charged. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Why do the counts differ between countries, and from the app?**

TikTok serves each country its own view of an account; the differences are small for followers (under 0.5% on 96 accounts we read from the US and Germany) and sometimes one video for the video count. The app also rounds counts above 10,000. Every row says which country read it, and carries the exact and the rounded number.

**What do COMPLETE, TRUNCATED, PARTIAL and CAPPED mean?**

On a profile row with videos: COMPLETE = every video asked was delivered, or every video TikTok lists; TRUNCATED = TikTok stopped serving the list before the end (videos\_note says how far it got against TikTok's count); PARTIAL = a read failed and the rest was delivered (a video page that failed leaves a row with the embed's fields only); CAPPED = maxItems or your maximum charge cut the videos.

**Why are the 10 newest videos faster than more?**

TikTok lists a profile's videos only to a real browser. The 10 newest are listed on TikTok's embed page and read from each video's own page without a browser; more, Popular or Oldest order, a window past the newest ten, or leaving pinned videos out need one browser page load per profile.

**What is never charged?**

Accounts that do not exist, are private, are hidden in the read country, could not be read or have no videos; playlist rows; the engagement figures; a file an earlier run already stored; a profile skipped by onlyChanged; and any run that failed.

**Can I find accounts by keyword?**

Not in this version. TikTok's account search is served only to a browser, and on Apify's servers it was not served to ours in any of seven tries (2026-10-01), so this Actor does not offer an input that would fail. Give the accounts by handle, URL, id or share link.

**Can I get comments, the account's region, or emails?**

No. Comments are served per video only to a browser session, so they are not part of this actor. TikTok does not show an account's region to a logged-out reader (actors that output one invent it). Contact finding is not built.

**How are the engagement figures computed?**

From the profile's own videos this run delivered (at least 3): engagement\_rate = mean of (likes + comments + shares + saves) / plays, in percent; engagement\_rate\_followers = mean interactions per video / followers, in percent; posts\_per\_week = videos / weeks between the oldest and the newest; creator\_score = 40 x min(1, log10(followers + 1) / 8) + 40 x min(1, engagement\_rate / 10) + 20 x min(1, posts\_per\_week / 7).

### Integration

#### JavaScript

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

#### Python

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

#### CLI

```bash
apify call hyperbach/tiktok-profile-scraper --input '{"profiles": ["notionhq"]}'
```

#### REST

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

### 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

## `profiles` (type: `array`):

TikTok accounts, one per line, in any form: a handle (notionhq or @notionhq), a profile URL, a video or photo URL (its author is read), a numeric user id, a secUid, a share link (vm.tiktok.com/…), or a playlist or collection URL (its videos are read). Handles are case-insensitive; an account named twice is read once.

## `readCountries` (type: `array`):

The country each account is read from. TikTok shows the counts and the videos of the reader's country (Notion: 331 videos to a US reader, 330 to a German one), so every row says which country read it. Two countries give two labelled profile rows per account; videos and lists are read from the first country.

## `maxVideosPerProfile` (type: `integer`):

How many of each profile's own videos to deliver; 0 = the profile row only. Up to 10 newest are read from TikTok's embed and each video's page; more are read by walking the profile's video list in a browser, to the end if you ask for more than the account has, with a COMPLETE or TRUNCATED verdict against TikTok's own count. A browser walk (more than 10, Popular or Oldest order, a date window past the newest 10, excludePinned) is charged once per profile as a video\_walk event, besides the videos.

## `videoOrder` (type: `string`):

Which end of the video list to read from: TikTok's own Latest, Popular and Oldest tabs.

## `videosNewerThan` (type: `string`):

Only videos posted on or after this date: 2026-05-01, or a span back from today such as 30 days, 2 weeks, 6 months, 1 year (UTC).

## `videosOlderThan` (type: `string`):

Only videos posted before this date: 2026-06-01, or a span back from today (UTC).

## `excludePinned` (type: `boolean`):

Skip the videos the account pinned to the top of its profile. Read in the browser, where TikTok marks them.

## `includeTranscripts` (type: `boolean`):

The spoken text of each video, from TikTok's own caption track (automatic or the creator's), in the video's language. A video without a caption track gets none; no AI transcription.

## `downloadMedia` (type: `string`):

Copy files into the run's key-value store, so the rows carry links that work as long as the store is kept (Apify's data retention for a run's default store; TikTok's own links expire within hours to days). Charged per file written.

## `maxRepostsPerProfile` (type: `integer`):

Videos the profile reposted, newest first, as video rows.

## `maxLikedPerProfile` (type: `integer`):

Videos the profile liked, where the account makes its liked tab public (most do not: the profile row's liked\_public says which).

## `includeStories` (type: `boolean`):

The profile's active stories as video rows (video\_source story), when it has any.

## `includePlaylists` (type: `boolean`):

One free row per playlist of the profile (name, video count, URL). Give a playlist URL in Profiles for its videos.

## `maxFollowersPerProfile` (type: `integer`):

Accounts that follow the profile, newest follower first, each with its own exact counts.

## `maxFollowingPerProfile` (type: `integer`):

Accounts the profile follows, where the account shows them.

## `outputFormat` (type: `string`):

Full gives flat rows with every field below. clockworks gives profile and video rows in the field names of the most used TikTok profile actor, so a pipeline built on it reads this one unchanged — with exact counts instead of rounded ones.

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

Stop after this many charged rows (profiles, videos and users together); 0 = no limit. Free rows (accounts not found, private, empty) do not count. The run also stops inside your maximum charge and says so.

## `onlyChanged` (type: `boolean`):

Monitoring: deliver (and charge) a profile only when a count, the handle, the bio or another watched field changed since the last run that delivered it. Each row carries the previous counts, the change and the change per day.

## `onlyNewVideos` (type: `boolean`):

Monitoring: skip videos an earlier run of the same task already delivered.

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

The name of the memory that keeps each account's counts between runs (a count history on every row, keyed by the user id, so a renamed account keeps its history). Set by itself from the accounts when onlyChanged or onlyNewVideos is on.

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

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

## `resumeFromRunId` (type: `string`):

The id of an earlier run of this actor with the same input that stopped before the end: this run reads only the accounts it had not finished, and delivers nothing twice.

## Actor input object example

```json
{
  "profiles": [
    "notionhq"
  ],
  "readCountries": [
    "US"
  ],
  "maxVideosPerProfile": 0,
  "videoOrder": "latest",
  "excludePinned": false,
  "includeTranscripts": false,
  "downloadMedia": "none",
  "maxRepostsPerProfile": 0,
  "maxLikedPerProfile": 0,
  "includeStories": false,
  "includePlaylists": false,
  "maxFollowersPerProfile": 0,
  "maxFollowingPerProfile": 0,
  "outputFormat": "full",
  "maxItems": 100,
  "onlyChanged": false,
  "onlyNewVideos": false
}
```

# Actor output Schema

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

All scraped records in the default dataset. One row per profile and read country, one per video, follower or following account, and playlist (row\_type says which):

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

How the run went, per account: the status by read country, TikTok's video count, videos asked and delivered, the route, the coverage verdict, the rows of each list, notes, seconds; why the run stopped early, if it did; and the source counters (requests, walls, rotations, browser loads).

# 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 = {
    "profiles": [
        "notionhq"
    ],
    "maxItems": 100
};

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

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

```

## MCP server setup

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